You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by gg...@apache.org on 2004/03/21 03:20:25 UTC
cvs commit: jakarta-commons/codec/src/java/org/apache/commons/codec/net QuotedPrintableCodec.java
ggregory 2004/03/20 18:20:25
Added: codec/src/test/org/apache/commons/codec/net
QuotedPrintableCodecTest.java
codec/src/java/org/apache/commons/codec/net
QuotedPrintableCodec.java
Log:
PR: Bugzilla Bug 27789
[Codec][PATCH] Quoted-printable codec (partial implementation)
Submitted by: Oleg Kalnichevski
Reviewed by: Gary Gregory
Revision Changes Path
1.1 jakarta-commons/codec/src/test/org/apache/commons/codec/net/QuotedPrintableCodecTest.java
Index: QuotedPrintableCodecTest.java
===================================================================
/*
* Copyright 2001-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.codec.net;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.EncoderException;
import junit.framework.TestCase;
/**
* Quoted-printable codec test cases
*
* @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
*/
public class QuotedPrintableCodecTest extends TestCase {
static final int SWISS_GERMAN_STUFF_UNICODE [] = {
0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4
};
static final int RUSSIAN_STUFF_UNICODE [] = {
0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438,
0x432, 0x435, 0x442
};
public QuotedPrintableCodecTest(String name) {
super(name);
}
private String constructString(int [] unicodeChars) {
StringBuffer buffer = new StringBuffer();
if (unicodeChars != null) {
for (int i = 0; i < unicodeChars.length; i++) {
buffer.append((char)unicodeChars[i]);
}
}
return buffer.toString();
}
public void testUTF8RoundTrip() throws Exception {
String ru_msg = constructString(RUSSIAN_STUFF_UNICODE);
String ch_msg = constructString(SWISS_GERMAN_STUFF_UNICODE);
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
assertEquals(
"=D0=92=D1=81=D0=B5=D0=BC_=D0=BF=D1=80=D0=B8=D0=B2=D0=B5=D1=82",
qpcodec.encode(ru_msg, "UTF-8")
);
assertEquals("Gr=C3=BCezi_z=C3=A4m=C3=A4", qpcodec.encode(ch_msg, "UTF-8"));
assertEquals(ru_msg, qpcodec.decode(qpcodec.encode(ru_msg, "UTF-8"), "UTF-8"));
assertEquals(ch_msg, qpcodec.decode(qpcodec.encode(ch_msg, "UTF-8"), "UTF-8"));
}
public void testBasicEncodeDecode() throws Exception {
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
String plain = "= Hello there =\r\n";
String encoded = qpcodec.encode(plain);
assertEquals("Basic quoted-printable encoding test",
"=3D Hello there =3D=0D=0A", encoded);
assertEquals("Basic quoted-printable decoding test",
plain, qpcodec.decode(encoded));
}
public void testSafeCharEncodeDecode() throws Exception {
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
String plain = "abc123_-.*~!@#$%^&()+{}\"\\;:`,/[]";
String encoded = qpcodec.encode(plain);
assertEquals("Safe chars quoted-printable encoding test",
plain, encoded);
assertEquals("Safe chars quoted-printable decoding test",
plain, qpcodec.decode(encoded));
}
public void testUnsafeEncodeDecode() throws Exception {
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
String plain = "=\r\n";
String encoded = qpcodec.encode(plain);
assertEquals("Unsafe chars quoted-printable encoding test",
"=3D=0D=0A", encoded);
assertEquals("Unsafe chars quoted-printable decoding test",
plain, qpcodec.decode(encoded));
}
public void testEncodeDecodeNull() throws Exception {
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
assertNull("Null string quoted-printable encoding test",
qpcodec.encode((String)null));
assertNull("Null string quoted-printable decoding test",
qpcodec.decode((String)null));
}
public void testDecodeInvalid() throws Exception {
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
try {
qpcodec.decode("=");
fail("DecoderException should have been thrown");
} catch(DecoderException e) {
// Expected. Move on
}
try {
qpcodec.decode("=A");
fail("DecoderException should have been thrown");
} catch(DecoderException e) {
// Expected. Move on
}
try {
qpcodec.decode("=WW");
fail("DecoderException should have been thrown");
} catch(DecoderException e) {
// Expected. Move on
}
}
public void testEncodeNull() throws Exception {
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
byte[] plain = null;
byte[] encoded = qpcodec.encode(plain);
assertEquals("Encoding a null string should return null",
null, encoded);
}
public void testEncodeUrlWithNullBitSet() throws Exception {
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
String plain = "1+1 = 2";
String encoded = new String(QuotedPrintableCodec.
encodeQuotedPrintable(null, plain.getBytes()));
assertEquals("Basic quoted-printable encoding test",
"1+1 =3D 2", encoded);
assertEquals("Basic quoted-printable decoding test",
plain, qpcodec.decode(encoded));
}
public void testDecodeWithNullArray() throws Exception {
byte[] plain = null;
byte[] result = QuotedPrintableCodec.decodeQuotedPrintable( plain );
assertEquals("Result should be null", null, result);
}
public void testEncodeStringWithNull() throws Exception {
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
String test = null;
String result = qpcodec.encode( test, "charset" );
assertEquals("Result should be null", null, result);
}
public void testDecodeStringWithNull() throws Exception {
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
String test = null;
String result = qpcodec.decode( test, "charset" );
assertEquals("Result should be null", null, result);
}
public void testEncodeObjects() throws Exception {
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
String plain = "1+1 = 2";
String encoded = (String) qpcodec.encode((Object) plain);
assertEquals("Basic quoted-printable encoding test",
"1+1 =3D 2", encoded);
byte[] plainBA = plain.getBytes();
byte[] encodedBA = (byte[]) qpcodec.encode((Object) plainBA);
encoded = new String(encodedBA);
assertEquals("Basic quoted-printable encoding test",
"1+1 =3D 2", encoded);
Object result = qpcodec.encode((Object) null);
assertEquals( "Encoding a null Object should return null", null, result);
try {
Object dObj = new Double(3.0);
qpcodec.encode( dObj );
fail( "Trying to url encode a Double object should cause an exception.");
} catch( EncoderException ee ) {
// Exception expected, test segment passes.
}
}
public void testInvalidEncoding() {
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec("NONSENSE");
String plain = "Hello there!";
try {
qpcodec.encode(plain);
fail( "We set the encoding to a bogus NONSENSE vlaue, this shouldn't have worked.");
} catch( EncoderException ee ) {
// Exception expected, test segment passes.
}
try {
qpcodec.decode(plain);
fail( "We set the encoding to a bogus NONSENSE vlaue, this shouldn't have worked.");
} catch( DecoderException ee ) {
// Exception expected, test segment passes.
}
}
public void testDecodeObjects() throws Exception {
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
String plain = "1+1 =3D 2";
String decoded = (String) qpcodec.decode((Object) plain);
assertEquals("Basic quoted-printable decoding test",
"1+1 = 2", decoded);
byte[] plainBA = plain.getBytes();
byte[] decodedBA = (byte[]) qpcodec.decode((Object) plainBA);
decoded = new String(decodedBA);
assertEquals("Basic quoted-printable decoding test",
"1+1 = 2", decoded);
Object result = qpcodec.decode((Object) null);
assertEquals( "Decoding a null Object should return null", null, result);
try {
Object dObj = new Double(3.0);
qpcodec.decode( dObj );
fail( "Trying to url encode a Double object should cause an exception.");
} catch( DecoderException ee ) {
// Exception expected, test segment passes.
}
}
public void testDefaultEncoding() throws Exception {
String plain = "Hello there!";
QuotedPrintableCodec qpcodec = new QuotedPrintableCodec("UnicodeBig");
qpcodec.encode(plain); // To work around a weird quirk in Java 1.2.2
String encoded1 = qpcodec.encode(plain, "UnicodeBig");
String encoded2 = qpcodec.encode(plain);
assertEquals(encoded1, encoded2);
}
}
1.1 jakarta-commons/codec/src/java/org/apache/commons/codec/net/QuotedPrintableCodec.java
Index: QuotedPrintableCodec.java
===================================================================
/*
* Copyright 2001-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.codec.net;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.BitSet;
import org.apache.commons.codec.BinaryDecoder;
import org.apache.commons.codec.BinaryEncoder;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.EncoderException;
import org.apache.commons.codec.StringDecoder;
import org.apache.commons.codec.StringEncoder;
/**
* <p>
* Codec for RFC 1521 MIME (Multipurpose Internet Mail Extensions) Part One.
* </p>
* <p>
* The Quoted-Printable encoding is intended to represent data that
* largely consists of octets that correspond to printable characters in
* the ASCII character set. It encodes the data in such a way that the
* resulting octets are unlikely to be modified by mail transport. If
* the data being encoded are mostly ASCII text, the encoded form of the
* data remains largely recognizable by humans. A body which is
* entirely ASCII may also be encoded in Quoted-Printable to ensure the
* integrity of the data should the message pass through a character-
* translating, and/or line-wrapping gateway.
* </p>
*
* <p>
* Note:
* </p>
* <p>
* Not implemented: rule #3, rule #4, rule #5 of the quoted-printable
* spec, because the complete quoted-printable spec does not lend itself well
* into the byte[] oriented codec framework. Complete the codec once the
* steamable codec framework is ready. The motivation behind providing the codec in
* a partial form is that it can already come in handy for those applications that
* do not require quoted-printable line formatting (rules #3, #4, #5), for instance
* Q codec.
* </p>
*
* @see <a href="http://http://www.ietf.org/rfc/rfc1521.txt">
* MIME (Multipurpose Internet Mail Extensions) Part One</a>
*
* @author Apache Software Foundation
* @since 1.3
* @version $Id: QuotedPrintableCodec.java,v 1.1 2004/03/21 02:20:25 ggregory Exp $
*/
public class QuotedPrintableCodec
implements BinaryEncoder, BinaryDecoder,
StringEncoder, StringDecoder
{
private final static String US_ASCII = "US-ASCII";
/**
* The default charset used for string decoding and encoding.
*/
protected String charset = "UTF-8";
/**
* BitSet of printable characters as defined in RFC 1521.
*/
protected static final BitSet PRINTABLE_CHARS = new BitSet(256);
protected static byte ESCAPE_CHAR = '=';
protected static byte TAB = 9;
protected static byte CR = 13;
protected static byte LF = 10;
protected static byte SPACE = 32;
// Static initializer for printable chars collection
static {
// alpha characters
for (int i = 33; i <= 60; i++) {
PRINTABLE_CHARS.set(i);
}
for (int i = 62; i <= 126; i++) {
PRINTABLE_CHARS.set(i);
}
PRINTABLE_CHARS.set(TAB);
PRINTABLE_CHARS.set(SPACE);
}
/**
* Default constructor.
*/
public QuotedPrintableCodec() {
super();
}
/**
* Constructor which allows for the selection of a default charset
*
* @param charset the default string charset to use.
*/
public QuotedPrintableCodec(String charset) {
super();
this.charset = charset;
}
/**
* Encodes byte into its quoted-printable representation.
*
* @param b byte to encode
* @param buffer the buffer to write to
*/
private static final void encodeQuotedPrintable(int b, ByteArrayOutputStream buffer)
{
buffer.write(ESCAPE_CHAR);
char hex1 = Character.toUpperCase(
Character.forDigit((b >> 4) & 0xF, 16));
char hex2 = Character.toUpperCase(
Character.forDigit(b & 0xF, 16));
buffer.write(hex1);
buffer.write(hex2);
}
/**
* Encodes an array of bytes into an array of quoted-printable 7-bit
* characters. Unsafe characters are escaped.
*
* <p>This function implements a subset of quoted-printable encoding
* specification (rule #1 and rule #2) as defined in RFC 1521 and is
* suitable for encoding binary data and unformatted text.
* </p>
*
* @param printable bitset of characters deemed quoted-printable
* @param pArray array of bytes to be encoded
* @return array of bytes containing quoted-printable data
*/
public static final byte[] encodeQuotedPrintable(BitSet printable, byte[] pArray) {
if (pArray == null) {
return null;
}
if (printable == null) {
printable = PRINTABLE_CHARS;
}
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (int i = 0; i < pArray.length; i++) {
int b = pArray[i];
if (b < 0) {
b = 256 + b;
}
if (printable.get(b)) {
buffer.write(b);
} else {
encodeQuotedPrintable(b, buffer);
}
}
return buffer.toByteArray();
}
/**
* Decodes an array quoted-printable characters into an array of
* original bytes. Escaped characters are converted back to their
* original representation.
*
* <p>This function implements a subset of quoted-printable encoding
* specification (rule #1 and rule #2) as defined in RFC 1521.
* </p>
*
* @param pArray array of quoted-printable characters
* @return array of original bytes
* @throws DecoderException Thrown if quoted-printable decoding is unsuccessful
*/
public static final byte[] decodeQuotedPrintable(byte[] pArray)
throws DecoderException
{
if (pArray == null) {
return null;
}
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (int i = 0; i < pArray.length; i++) {
int b = pArray[i];
if (b == ESCAPE_CHAR) {
try {
int u = Character.digit((char)pArray[++i], 16);
int l = Character.digit((char)pArray[++i], 16);
if (u == -1 || l == -1) {
throw new DecoderException("Invalid quoted-printable encoding");
}
buffer.write((char)((u << 4) + l));
} catch(ArrayIndexOutOfBoundsException e) {
throw new DecoderException("Invalid quoted-printable encoding");
}
} else {
buffer.write(b);
}
}
return buffer.toByteArray();
}
/**
* Encodes an array of bytes into an array of quoted-printable 7-bit
* characters. Unsafe characters are escaped.
*
* <p>This function implements a subset of quoted-printable encoding
* specification (rule #1 and rule #2) as defined in RFC 1521 and is
* suitable for encoding binary data and unformatted text.
* </p>
*
* @param pArray array of bytes to be encoded
* @return array of bytes containing quoted-printable data
*/
public byte[] encode(byte[] pArray) {
return encodeQuotedPrintable(PRINTABLE_CHARS, pArray);
}
/**
* Decodes an array of quoted-printable characters into an array of
* original bytes. Escaped characters are converted back to their
* original representation.
*
* <p>This function implements a subset of quoted-printable encoding
* specification (rule #1 and rule #2) as defined in RFC 1521.
* </p>
*
* @param pArray array of quoted-printable characters
* @return array of original bytes
* @throws DecoderException Thrown if quoted-printable decoding is unsuccessful
*/
public byte[] decode(byte[] pArray) throws DecoderException {
return decodeQuotedPrintable(pArray);
}
/**
* Encodes a string into its quoted-printable form using the default
* string charset. Unsafe characters are escaped.
*
* <p>This function implements a subset of quoted-printable encoding
* specification (rule #1 and rule #2) as defined in RFC 1521 and is
* suitable for encoding binary data.
* </p>
*
* @param pString string to convert to quoted-printable form
* @return quoted-printable string
*
* @throws EncoderException Thrown if quoted-printable encoding is unsuccessful
*
* @see #getDefaultCharset()
*/
public String encode(String pString) throws EncoderException {
if (pString == null) {
return null;
}
try {
return encode(pString, getDefaultCharset());
} catch(UnsupportedEncodingException e) {
throw new EncoderException(e.getMessage());
}
}
/**
* Decodes a quoted-printable string into its original form using the
* specified string charset. Escaped characters are converted back
* to their original representation.
*
* @param pString quoted-printable string to convert into its original form
* @param charset the original string charset
* @return original string
* @throws DecoderException Thrown if quoted-printable decoding is
* unsuccessful
* @throws UnsupportedEncodingException Thrown if charset is not
* supported
*/
public String decode(String pString, String charset)
throws DecoderException, UnsupportedEncodingException
{
if (pString == null) {
return null;
}
return new String(decode(pString.getBytes(US_ASCII)), charset);
}
/**
* Decodes a quoted-printable string into its original form using the
* default string charset. Escaped characters are converted back
* to their original representation.
*
* @param pString quoted-printable string to convert into its original form
* @return original string
* @throws DecoderException Thrown if quoted-printable decoding is
* unsuccessful
* @throws UnsupportedEncodingException Thrown if charset is not
* supported
* @see #getDefaultCharset()
*/
public String decode(String pString) throws DecoderException {
if (pString == null) {
return null;
}
try {
return decode(pString, getDefaultCharset());
} catch(UnsupportedEncodingException e) {
throw new DecoderException(e.getMessage());
}
}
/**
* Encodes an object into its quoted-printable safe form. Unsafe
* characters are escaped.
*
* @param pObject string to convert to a quoted-printable form
* @return quoted-printable object
* @throws EncoderException Thrown if quoted-printable encoding is not
* applicable to objects of this type or
* if encoding is unsuccessful
*/
public Object encode(Object pObject) throws EncoderException {
if (pObject == null) {
return null;
} else if (pObject instanceof byte[]) {
return encode((byte[])pObject);
} else if (pObject instanceof String) {
return encode((String)pObject);
} else {
throw new EncoderException("Objects of type " +
pObject.getClass().getName() + " cannot be quoted-printable encoded");
}
}
/**
* Decodes a quoted-printable object into its original form. Escaped
* characters are converted back to their original representation.
*
* @param pObject quoted-printable object to convert into its original form
* @return original object
* @throws DecoderException Thrown if quoted-printable decoding is not
* applicable to objects of this type
* if decoding is unsuccessful
*/
public Object decode(Object pObject) throws DecoderException {
if (pObject == null) {
return null;
} else if (pObject instanceof byte[]) {
return decode((byte[])pObject);
} else if (pObject instanceof String) {
return decode((String)pObject);
} else {
throw new DecoderException("Objects of type " +
pObject.getClass().getName() + " cannot be quoted-printable decoded");
}
}
/**
* Returns the default charset used for string decoding and encoding.
*
* @return the default string charset.
*/
public String getDefaultCharset() {
return this.charset;
}
/**
* Encodes a string into its quoted-printable form using the specified
* charset. Unsafe characters are escaped.
*
* <p>This function implements a subset of quoted-printable encoding
* specification (rule #1 and rule #2) as defined in RFC 1521 and is
* suitable for encoding binary data and unformatted text.
* </p>
*
* @param pString string to convert to quoted-printable form
* @param charset the charset for pString
* @return quoted-printable string
*
* @throws UnsupportedEncodingException Thrown if the charset is not
* supported
*/
public String encode(String pString, String charset)
throws UnsupportedEncodingException
{
if (pString == null) {
return null;
}
return new String(encode(pString.getBytes(charset)), US_ASCII);
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org