You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by ri...@apache.org on 2008/02/13 19:27:26 UTC
svn commit: r627556 - in /geronimo/specs/trunk/geronimo-javamail_1.4_spec:
./ src/main/java/javax/mail/internet/
src/main/java/org/apache/geronimo/mail/util/
src/test/java/javax/mail/internet/
Author: rickmcguire
Date: Wed Feb 13 10:27:22 2008
New Revision: 627556
URL: http://svn.apache.org/viewvc?rev=627556&view=rev
Log:
GERONIMO-3842 Incorrect Subject header encoding for long non-ascii line
Modified:
geronimo/specs/trunk/geronimo-javamail_1.4_spec/pom.xml
geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeBodyPart.java
geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeUtility.java
geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java
geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java
geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeMessageTest.java
geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeTest.java
Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/pom.xml
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/pom.xml?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/pom.xml (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/pom.xml Wed Feb 13 10:27:22 2008
@@ -34,7 +34,7 @@
<artifactId>geronimo-javamail_1.4_spec</artifactId>
<packaging>bundle</packaging>
<name>JavaMail 1.4</name>
- <version>1.2</version>
+ <version>1.3-SNAPSHOT</version>
<properties>
<geronimo.osgi.export.pkg>javax.mail*</geronimo.osgi.export.pkg>
Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeBodyPart.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeBodyPart.java?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeBodyPart.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeBodyPart.java Wed Feb 13 10:27:22 2008
@@ -327,7 +327,6 @@
public void setFileName(String name) throws MessagingException {
- System.out.println("Setting file name to " + name);
// there's an optional session property that requests file name encoding...we need to process this before
// setting the value.
if (name != null && SessionUtil.getBooleanProperty(MIME_ENCODEFILENAME, false)) {
Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeUtility.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeUtility.java?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeUtility.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MimeUtility.java Wed Feb 13 10:27:22 2008
@@ -567,23 +567,132 @@
}
try {
- // get the string bytes in the correct source charset
- InputStream in = new ByteArrayInputStream(word.getBytes( javaCharset(charset)));
- ByteArrayOutputStream out = new ByteArrayOutputStream();
-
+
+ // we'll format this directly into the string buffer
+ StringBuffer result = new StringBuffer();
+
+ // this is the maximum size of a segment of encoded data, which is based off
+ // of a 75 character size limit and all of the encoding overhead elements.
+ int sizeLimit = 75 - 7 - charset.length();
+
+ // now do the appropriate encoding work
if (encoder.equals("base64")) {
Base64Encoder dataEncoder = new Base64Encoder();
- dataEncoder.encodeWord(in, charset, out, SessionUtil.getBooleanProperty(MIME_FOLDENCODEDWORDS, false));
+ // this may recurse on the encoding if the string is too long. The left-most will not
+ // get a segment delimiter
+ encodeBase64(word, result, sizeLimit, charset, dataEncoder, true, SessionUtil.getBooleanProperty(MIME_FOLDENCODEDWORDS, false));
}
else {
QuotedPrintableEncoder dataEncoder = new QuotedPrintableEncoder();
- dataEncoder.encodeWord(in, charset, encodingWord ? QP_WORD_SPECIALS : QP_TEXT_SPECIALS, out, SessionUtil.getBooleanProperty(MIME_FOLDENCODEDWORDS, false));
+ encodeQuotedPrintable(word, result, sizeLimit, charset, dataEncoder, true,
+ SessionUtil.getBooleanProperty(MIME_FOLDENCODEDWORDS, false), encodingWord ? QP_WORD_SPECIALS : QP_TEXT_SPECIALS);
}
-
- byte[] bytes = out.toByteArray();
- return new String(bytes);
+ return result.toString();
} catch (IOException e) {
throw new UnsupportedEncodingException("Invalid encoding");
+ }
+ }
+
+
+ /**
+ * Encode a string into base64 encoding, taking into
+ * account the maximum segment length.
+ *
+ * @param data The string data to encode.
+ * @param out The output buffer used for the result.
+ * @param sizeLimit The maximum amount of encoded data we're allowed
+ * to have in a single encoded segment.
+ * @param charset The character set marker that needs to be added to the
+ * encoding header.
+ * @param encoder The encoder instance we're using.
+ * @param firstSegment
+ * If true, this is the first (left-most) segment in the
+ * data. Used to determine if segment delimiters need to
+ * be added between sections.
+ * @param foldSegments
+ * Indicates the type of delimiter to use (blank or newline sequence).
+ */
+ static private void encodeBase64(String data, StringBuffer out, int sizeLimit, String charset, Base64Encoder encoder, boolean firstSegment, boolean foldSegments) throws IOException
+ {
+ // this needs to be converted into the appropriate transfer encoding.
+ byte [] bytes = data.getBytes(javaCharset(charset));
+
+ int estimatedSize = encoder.estimateEncodedLength(bytes);
+
+ // if the estimated encoding size is over our segment limit, split the string in half and
+ // recurse. Eventually we'll reach a point where things are small enough.
+ if (estimatedSize > sizeLimit) {
+ // the first segment indicator travels with the left half.
+ encodeBase64(data.substring(0, data.length() / 2), out, sizeLimit, charset, encoder, firstSegment, foldSegments);
+ // the second half can never be the first segment
+ encodeBase64(data.substring(data.length() / 2), out, sizeLimit, charset, encoder, false, foldSegments);
+ }
+ else
+ {
+ // if this is not the first sement of the encoding, we need to add either a blank or
+ // a newline sequence to the data
+ if (!firstSegment) {
+ if (foldSegments) {
+ out.append("\r\n");
+ }
+ else {
+ out.append(' ');
+ }
+ }
+ // do the encoding of the segment.
+ encoder.encodeWord(bytes, out, charset);
+ }
+ }
+
+
+ /**
+ * Encode a string into quoted printable encoding, taking into
+ * account the maximum segment length.
+ *
+ * @param data The string data to encode.
+ * @param out The output buffer used for the result.
+ * @param sizeLimit The maximum amount of encoded data we're allowed
+ * to have in a single encoded segment.
+ * @param charset The character set marker that needs to be added to the
+ * encoding header.
+ * @param encoder The encoder instance we're using.
+ * @param firstSegment
+ * If true, this is the first (left-most) segment in the
+ * data. Used to determine if segment delimiters need to
+ * be added between sections.
+ * @param foldSegments
+ * Indicates the type of delimiter to use (blank or newline sequence).
+ */
+ static private void encodeQuotedPrintable(String data, StringBuffer out, int sizeLimit, String charset, QuotedPrintableEncoder encoder,
+ boolean firstSegment, boolean foldSegments, String specials) throws IOException
+ {
+ // this needs to be converted into the appropriate transfer encoding.
+ byte [] bytes = data.getBytes(javaCharset(charset));
+
+ int estimatedSize = encoder.estimateEncodedLength(bytes, specials);
+
+ // if the estimated encoding size is over our segment limit, split the string in half and
+ // recurse. Eventually we'll reach a point where things are small enough.
+ if (estimatedSize > sizeLimit) {
+ // the first segment indicator travels with the left half.
+ encodeQuotedPrintable(data.substring(0, data.length() / 2), out, sizeLimit, charset, encoder, firstSegment, foldSegments, specials);
+ // the second half can never be the first segment
+ encodeQuotedPrintable(data.substring(data.length() / 2), out, sizeLimit, charset, encoder, false, foldSegments, specials);
+ }
+ else
+ {
+ // if this is not the first sement of the encoding, we need to add either a blank or
+ // a newline sequence to the data
+ if (!firstSegment) {
+ if (foldSegments) {
+ out.append("\r\n");
+ }
+ else {
+ out.append(' ');
+ }
+ }
+ // do the encoding of the segment.
+ encoder.encodeWord(bytes, out, charset, specials);
}
}
Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java Wed Feb 13 10:27:22 2008
@@ -464,7 +464,7 @@
PrintStream writer = new PrintStream(out);
// encoded words are restricted to 76 bytes, including the control adornments.
- int limit = 76 - 7 - charset.length();
+ int limit = 75 - 7 - charset.length();
boolean firstLine = true;
StringBuffer encodedString = new StringBuffer(76);
@@ -499,9 +499,95 @@
// reset our string buffer for the next segment.
encodedString.setLength(0);
+ // we need a delimiter after this
+ firstLine = false;
}
}
+
+ /**
+ * Perform RFC-2047 word encoding using Base64 data encoding.
+ *
+ * @param in The source for the encoded data.
+ * @param charset The charset tag to be added to each encoded data section.
+ * @param out The output stream where the encoded data is to be written.
+ * @param fold Controls whether separate sections of encoded data are separated by
+ * linebreaks or whitespace.
+ *
+ * @exception IOException
+ */
+ public void encodeWord(byte[] data, StringBuffer out, String charset) throws IOException
+ {
+ // append the word header
+ out.append("=?");
+ out.append(charset);
+ out.append("?B?");
+ // add on the encodeded data
+ encodeWordData(data, out);
+ // the end of the encoding marker
+ out.append("?=");
+ }
+
+ /**
+ * encode the input data producing a base 64 output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public void encodeWordData(byte[] data, StringBuffer out)
+ {
+ int modulus = data.length % 3;
+ int dataLength = (data.length - modulus);
+ int a1, a2, a3;
+
+ for (int i = 0; i < dataLength; i += 3)
+ {
+ a1 = data[i] & 0xff;
+ a2 = data[i + 1] & 0xff;
+ a3 = data[i + 2] & 0xff;
+
+ out.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
+ out.append((char)encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
+ out.append((char)encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
+ out.append((char)encodingTable[a3 & 0x3f]);
+ }
+
+ /*
+ * process the tail end.
+ */
+ int b1, b2, b3;
+ int d1, d2;
+
+ switch (modulus)
+ {
+ case 0: /* nothing left to do */
+ break;
+ case 1:
+ d1 = data[dataLength] & 0xff;
+ b1 = (d1 >>> 2) & 0x3f;
+ b2 = (d1 << 4) & 0x3f;
+
+ out.append((char)encodingTable[b1]);
+ out.append((char)encodingTable[b2]);
+ out.append((char)padding);
+ out.append((char)padding);
+ break;
+ case 2:
+ d1 = data[dataLength] & 0xff;
+ d2 = data[dataLength + 1] & 0xff;
+
+ b1 = (d1 >>> 2) & 0x3f;
+ b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
+ b3 = (d2 << 2) & 0x3f;
+
+ out.append((char)encodingTable[b1]);
+ out.append((char)encodingTable[b2]);
+ out.append((char)encodingTable[b3]);
+ out.append((char)padding);
+ break;
+ }
+ }
+
+
/**
* encode the input data producing a base 64 output stream.
*
@@ -520,7 +606,7 @@
int a1 = inBuffer[0] & 0xff;
int a2 = inBuffer[1] & 0xff;
int a3 = inBuffer[2] & 0xff;
-
+
out.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
out.append((char)encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
out.append((char)encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
@@ -550,5 +636,22 @@
return;
}
}
+ }
+
+
+ /**
+ * Estimate the final encoded size of a segment of data.
+ * This is used to ensure that the encoded blocks do
+ * not get split across a unicode character boundary and
+ * that the encoding will fit within the bounds of
+ * a mail header line.
+ *
+ * @param data The data we're anticipating encoding.
+ *
+ * @return The size of the byte data in encoded form.
+ */
+ public int estimateEncodedLength(byte[] data)
+ {
+ return ((data.length + 2) / 3) * 4;
}
}
Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java Wed Feb 13 10:27:22 2008
@@ -636,8 +636,8 @@
PushbackInputStream inStream = new PushbackInputStream(in);
PrintStream writer = new PrintStream(out);
- // segments of encoded data are limited to 76 byes, including the control sections.
- int limit = 76 - 7 - charset.length();
+ // segments of encoded data are limited to 75 byes, including the control sections.
+ int limit = 75 - 7 - charset.length();
boolean firstLine = true;
StringBuffer encodedString = new StringBuffer(76);
@@ -672,7 +672,102 @@
// we reset the string buffer and reuse it.
encodedString.setLength(0);
+ // we need a delimiter between sections from this point on.
+ firstLine = false;
}
+ }
+
+
+ /**
+ * Perform RFC-2047 word encoding using Base64 data encoding.
+ *
+ * @param in The source for the encoded data.
+ * @param charset The charset tag to be added to each encoded data section.
+ * @param out The output stream where the encoded data is to be written.
+ * @param fold Controls whether separate sections of encoded data are separated by
+ * linebreaks or whitespace.
+ *
+ * @exception IOException
+ */
+ public void encodeWord(byte[] data, StringBuffer out, String charset, String specials) throws IOException
+ {
+ // append the word header
+ out.append("=?");
+ out.append(charset);
+ out.append("?Q?");
+ // add on the encodeded data
+ encodeWordData(data, out, specials);
+ // the end of the encoding marker
+ out.append("?=");
+ }
+
+
+ /**
+ * Perform RFC-2047 word encoding using Q-P data encoding.
+ *
+ * @param in The source for the encoded data.
+ * @param charset The charset tag to be added to each encoded data section.
+ * @param specials The set of special characters that we require to encoded.
+ * @param out The output stream where the encoded data is to be written.
+ * @param fold Controls whether separate sections of encoded data are separated by
+ * linebreaks or whitespace.
+ *
+ * @exception IOException
+ */
+ public void encodeWordData(byte[] data, StringBuffer out, String specials) throws IOException {
+ for (int i = 0; i < data.length; i++) {
+ int ch = data[i] & 0xff; ;
+
+ // spaces require special handling. If the next character is a line terminator, then
+ // the space needs to be encoded.
+ if (ch == ' ') {
+ // blanks get translated into underscores, because the encoded tokens can't have embedded blanks.
+ out.append('_');
+ }
+ // non-ascii chars and the designated specials all get encoded.
+ else if (ch < 32 || ch >= 127 || specials.indexOf(ch) != -1) {
+ out.append('=');
+ out.append((char)encodingTable[ch >> 4]);
+ out.append((char)encodingTable[ch & 0x0F]);
+ }
+ else {
+ // good character, just use unchanged.
+ out.append((char)ch);
+ }
+ }
+ }
+
+
+ /**
+ * Estimate the final encoded size of a segment of data.
+ * This is used to ensure that the encoded blocks do
+ * not get split across a unicode character boundary and
+ * that the encoding will fit within the bounds of
+ * a mail header line.
+ *
+ * @param data The data we're anticipating encoding.
+ *
+ * @return The size of the byte data in encoded form.
+ */
+ public int estimateEncodedLength(byte[] data, String specials)
+ {
+ int count = 0;
+
+ for (int i = 0; i < data.length; i++) {
+ // make sure this is just a single byte value.
+ int ch = data[i] & 0xff;
+
+ // non-ascii chars and the designated specials all get encoded.
+ if (ch < 32 || ch >= 127 || specials.indexOf(ch) != -1) {
+ // Q encoding translates a single char into 3 characters
+ count += 3;
+ }
+ else {
+ // non-encoded character
+ count++;
+ }
+ }
+ return count;
}
}
Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeMessageTest.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeMessageTest.java?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeMessageTest.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeMessageTest.java Wed Feb 13 10:27:22 2008
@@ -67,7 +67,6 @@
assertEquals(((InternetAddress)newMessage.getSender()).getAddress(), "foo");
String[] headers = newMessage.getHeader("foo");
- System.out.println("Get header returned " + headers);
assertTrue(headers.length == 1);
assertEquals(headers[0], "bar");
@@ -188,7 +187,6 @@
recipients = msg.getAllRecipients();
- System.out.println("Get all recipients returns " + recipients.length);
assertTrue(recipients.length == 3);
assertEquals(recipients[0], dev);
assertEquals(recipients[1], user);
@@ -266,7 +264,6 @@
msg.setRecipients(type, "geronimo-dev");
recipients = msg.getRecipients(type);
assertTrue(recipients.length == 1);
- System.out.println("Received address " + recipients[0] + " of type " + recipients[0].getType());
assertEquals(recipients[0], dev);
msg.addRecipients(type, "geronimo-user");
Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeTest.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeTest.java?rev=627556&r1=627555&r2=627556&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeTest.java (original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MimeTest.java Wed Feb 13 10:27:22 2008
@@ -52,8 +52,6 @@
ByteArrayOutputStream baos = new ByteArrayOutputStream();
mime.writeTo(baos);
- System.out.println(baos);
-
MimeMessage mime2 = new MimeMessage(session, new ByteArrayInputStream(baos.toByteArray()));
assertTrue(mime2.getContent() instanceof MimeMultipart);
MimeMultipart parts2 = (MimeMultipart) mime2.getContent();