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