4242 * to ensure the integrity of the data should the message pass through a character- translating, and/or line-wrapping
4343 * gateway.
4444 * </p>
45- *
46- * <p>
47- * Note:
48- * </p>
49- * <p>
50- * Rules #3, #4, and #5 of the quoted-printable spec are not implemented yet because the complete quoted-printable spec
51- * does not lend itself well into the byte[] oriented codec framework. Complete the codec once the streamable codec
52- * framework is ready. The motivation behind providing the codec in a partial form is that it can already come in handy
53- * for those applications that do not require quoted-printable line formatting (rules #3, #4, #5), for instance Q codec.
54- * </p>
55- *
45+ *
5646 * @see <a href="http://www.ietf.org/rfc/rfc1521.txt"> RFC 1521 MIME (Multipurpose Internet Mail Extensions) Part One:
5747 * Mechanisms for Specifying and Describing the Format of Internet Message Bodies </a>
58- *
48+ *
5949 * @author Apache Software Foundation
6050 * @since 1.3
6151 * @version $Id$
@@ -76,6 +66,14 @@ public class QuotedPrintableCodec implements BinaryEncoder, BinaryDecoder, Strin
7666 private static final byte TAB = 9 ;
7767
7868 private static final byte SPACE = 32 ;
69+
70+ private static final byte CR = 13 ;
71+
72+ private static final byte LF = 10 ;
73+
74+ /** Safe line length for quoted printable encoded text. */
75+ private static final int SAFE_LENGTH = 73 ;
76+
7977 // Static initializer for printable chars collection
8078 static {
8179 // alpha characters
@@ -111,26 +109,78 @@ public QuotedPrintableCodec(String charset) {
111109 * Encodes byte into its quoted-printable representation.
112110 *
113111 * @param b
114- * byte to encode
112+ * byte to encode
115113 * @param buffer
116- * the buffer to write to
114+ * the buffer to write to
115+ * @return The number of bytes written to the <code>buffer</code>
117116 */
118- private static final void encodeQuotedPrintable (int b , ByteArrayOutputStream buffer ) {
117+ private static final int encodeQuotedPrintable (int b , ByteArrayOutputStream buffer ) {
119118 buffer .write (ESCAPE_CHAR );
120119 char hex1 = Character .toUpperCase (Character .forDigit ((b >> 4 ) & 0xF , 16 ));
121120 char hex2 = Character .toUpperCase (Character .forDigit (b & 0xF , 16 ));
122121 buffer .write (hex1 );
123122 buffer .write (hex2 );
123+ return 3 ;
124+ }
125+
126+ /**
127+ * Return the byte at position <code>index</code> of the byte array and
128+ * make sure it is unsigned.
129+ *
130+ * @param index
131+ * position in the array
132+ * @param bytes
133+ * the byte array
134+ * @return the unsigned octet at position <code>index</code> from the array
135+ */
136+ private static int getUnsignedOctet (final int index , final byte [] bytes ) {
137+ int b = bytes [index ];
138+ if (b < 0 ) {
139+ b = 256 + b ;
140+ }
141+ return b ;
142+ }
143+
144+ /**
145+ * Write a byte to the buffer.
146+ *
147+ * @param b
148+ * byte to write
149+ * @param encode
150+ * indicates whether the octet shall be encoded
151+ * @param buffer
152+ * the buffer to write to
153+ * @return the number of bytes that have been written to the buffer
154+ */
155+ private static int encodeByte (final int b , final boolean encode ,
156+ final ByteArrayOutputStream buffer ) {
157+ if (encode ) {
158+ return encodeQuotedPrintable (b , buffer );
159+ } else {
160+ buffer .write (b );
161+ return 1 ;
162+ }
163+ }
164+
165+ /**
166+ * Checks whether the given byte is whitespace.
167+ *
168+ * @param b
169+ * byte to be checked
170+ * @return <code>true</code> if the byte is either a space or tab character
171+ */
172+ private static boolean isWhitespace (final int b ) {
173+ return b == SPACE || b == TAB ;
124174 }
125175
126176 /**
127177 * Encodes an array of bytes into an array of quoted-printable 7-bit characters. Unsafe characters are escaped.
128- *
178+ *
129179 * <p>
130- * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
131- * RFC 1521 and is suitable for encoding binary data and unformatted text.
180+ * This function fully implements the quoted-printable encoding specification (rule #1 through rule #5)
181+ * as defined in RFC 1521 and is suitable for encoding binary data and unformatted text.
132182 * </p>
133- *
183+ *
134184 * @param printable
135185 * bitset of characters deemed quoted-printable
136186 * @param bytes
@@ -145,29 +195,59 @@ public static final byte[] encodeQuotedPrintable(BitSet printable, byte[] bytes)
145195 printable = PRINTABLE_CHARS ;
146196 }
147197 ByteArrayOutputStream buffer = new ByteArrayOutputStream ();
148- for (byte c : bytes ) {
149- int b = c ;
150- if (b < 0 ) {
151- b = 256 + b ;
152- }
153- if (printable .get (b )) {
154- buffer .write (b );
198+ int pos = 1 ;
199+ // encode up to buffer.length - 3, the last three octets will be treated
200+ // separately for simplification of note #3
201+ for (int i = 0 ; i < bytes .length - 3 ; i ++) {
202+ int b = getUnsignedOctet (i , bytes );
203+ if (pos < SAFE_LENGTH ) {
204+ // up to this length it is safe to add any byte, encoded or not
205+ pos += encodeByte (b , !printable .get (b ), buffer );
155206 } else {
156- encodeQuotedPrintable (b , buffer );
207+ // rule #3: whitespace at the end of a line *must* be encoded
208+ encodeByte (b , !printable .get (b ) || isWhitespace (b ), buffer );
209+
210+ // rule #5: soft line break
211+ buffer .write (ESCAPE_CHAR );
212+ buffer .write (CR );
213+ buffer .write (LF );
214+ pos = 1 ;
157215 }
158216 }
217+
218+ // rule #3: whitespace at the end of a line *must* be encoded
219+ // if we would do a soft break line after this octet, encode whitespace
220+ int b = getUnsignedOctet (bytes .length - 3 , bytes );
221+ boolean encode = !printable .get (b ) || (isWhitespace (b ) && pos > SAFE_LENGTH - 5 );
222+ pos += encodeByte (b , encode , buffer );
223+
224+ // note #3: '=' *must not* be the ultimate or penultimate character
225+ // simplification: if < 6 bytes left, do a soft line break as we may need
226+ // exactly 6 bytes space for the last 2 bytes
227+ if (pos > SAFE_LENGTH - 2 ) {
228+ buffer .write (ESCAPE_CHAR );
229+ buffer .write (CR );
230+ buffer .write (LF );
231+ }
232+ for (int i = bytes .length - 2 ; i < bytes .length ; i ++) {
233+ b = getUnsignedOctet (i , bytes );
234+ // rule #3: trailing whitespace shall be encoded
235+ encode = !printable .get (b ) || (i > bytes .length - 2 && isWhitespace (b ));
236+ encodeByte (b , encode , buffer );
237+ }
238+
159239 return buffer .toByteArray ();
160240 }
161241
162242 /**
163- * Decodes an array quoted-printable characters into an array of original bytes. Escaped characters are converted
164- * back to their original representation.
165- *
243+ * Decodes an array quoted-printable characters into an array of original bytes. Escaped characters are
244+ * converted back to their original representation.
245+ *
166246 * <p>
167- * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2 ) as defined in
168- * RFC 1521.
247+ * This function fully implements the quoted-printable encoding specification (rule #1 through rule #5 ) as
248+ * defined in RFC 1521.
169249 * </p>
170- *
250+ *
171251 * @param bytes
172252 * array of quoted-printable characters
173253 * @return array of original bytes
@@ -180,16 +260,21 @@ public static final byte[] decodeQuotedPrintable(byte[] bytes) throws DecoderExc
180260 }
181261 ByteArrayOutputStream buffer = new ByteArrayOutputStream ();
182262 for (int i = 0 ; i < bytes .length ; i ++) {
183- int b = bytes [i ];
263+ final int b = bytes [i ];
184264 if (b == ESCAPE_CHAR ) {
185265 try {
186- int u = Utils .digit16 (bytes [++i ]);
266+ // if the next octet is a CR we have found a soft line break
267+ if (bytes [++i ] == CR ) {
268+ continue ;
269+ }
270+ int u = Utils .digit16 (bytes [i ]);
187271 int l = Utils .digit16 (bytes [++i ]);
188272 buffer .write ((char ) ((u << 4 ) + l ));
189273 } catch (ArrayIndexOutOfBoundsException e ) {
190274 throw new DecoderException ("Invalid quoted-printable encoding" , e );
191275 }
192- } else {
276+ } else if (b != CR && b != LF ) {
277+ // every other octet is appended except for CR & LF
193278 buffer .write (b );
194279 }
195280 }
@@ -200,8 +285,8 @@ public static final byte[] decodeQuotedPrintable(byte[] bytes) throws DecoderExc
200285 * Encodes an array of bytes into an array of quoted-printable 7-bit characters. Unsafe characters are escaped.
201286 *
202287 * <p>
203- * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
204- * RFC 1521 and is suitable for encoding binary data and unformatted text.
288+ * This function fully implements the quoted-printable encoding specification (rule #1 through rule #5)
289+ * as defined in RFC 1521 and is suitable for encoding binary data and unformatted text.
205290 * </p>
206291 *
207292 * @param bytes
@@ -217,8 +302,8 @@ public byte[] encode(byte[] bytes) {
217302 * back to their original representation.
218303 *
219304 * <p>
220- * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
221- * RFC 1521.
305+ * This function fully implements the quoted-printable encoding specification (rule #1 through rule #2)
306+ * as defined in RFC 1521.
222307 * </p>
223308 *
224309 * @param bytes
@@ -235,8 +320,8 @@ public byte[] decode(byte[] bytes) throws DecoderException {
235320 * Encodes a string into its quoted-printable form using the default string charset. Unsafe characters are escaped.
236321 *
237322 * <p>
238- * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
239- * RFC 1521 and is suitable for encoding binary data.
323+ * This function fully implements the quoted-printable encoding specification (rule #1 through rule #2)
324+ * as defined in RFC 1521 and is suitable for encoding binary data.
240325 * </p>
241326 *
242327 * @param pString
@@ -365,8 +450,8 @@ public String getDefaultCharset() {
365450 * Encodes a string into its quoted-printable form using the specified charset. Unsafe characters are escaped.
366451 *
367452 * <p>
368- * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
369- * RFC 1521 and is suitable for encoding binary data and unformatted text.
453+ * This function fully implements the quoted-printable encoding specification (rule #1 through rule #2)
454+ * as defined in RFC 1521 and is suitable for encoding binary data and unformatted text.
370455 * </p>
371456 *
372457 * @param pString
0 commit comments