All Downloads are FREE. Search and download functionalities are using the official Maven repository.

panda.codec.net.QCodec Maven / Gradle / Ivy

Go to download

Panda Core is the core module of Panda Framework, it contains commonly used utility classes similar to apache-commons.

There is a newer version: 1.8.0
Show newest version
package panda.codec.net;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.BitSet;

import panda.codec.DecoderException;
import panda.codec.EncoderException;
import panda.codec.StringDecoder;
import panda.codec.StringEncoder;
import panda.io.stream.ByteArrayOutputStream;
import panda.lang.Charsets;

/**
 * Similar to the Quoted-Printable content-transfer-encoding defined in RFC 1521 and designed to allow text containing
 * mostly ASCII characters to be decipherable on an ASCII terminal without decoding.
 * 

* RFC 1522 describes techniques to allow the * encoding of non-ASCII text in various portions of a RFC 822 [2] message header, in a manner which * is unlikely to confuse existing message handling software. *

* This class is conditionally thread-safe. The instance field {@link #encodeBlanks} is mutable * {@link #setEncodeBlanks(boolean)} but is not volatile, and accesses are not synchronised. If an * instance of the class is shared between threads, the caller needs to ensure that suitable * synchronisation is used to ensure safe publication of the value between threads, and must not * invoke {@link #setEncodeBlanks(boolean)} after initial setup. * * @see MIME (Multipurpose Internet Mail Extensions) * Part Two: Message Header Extensions for Non-ASCII Text */ public class QCodec extends RFC1522Codec implements StringEncoder, StringDecoder { private static String WORD_SPECIALS = "=_?\"#$%&'(),.:;<>@[\\]^`{|}~"; private static String TEXT_SPECIALS = "=_?"; /** * The default charset used for string decoding and encoding. */ private final Charset charset; /** * BitSet of printable characters as defined in RFC 1522. */ private static final BitSet PRINTABLE_CHARS = new BitSet(256); // Static initializer for printable chars collection static { // alpha characters PRINTABLE_CHARS.set(' '); PRINTABLE_CHARS.set('!'); PRINTABLE_CHARS.set('"'); PRINTABLE_CHARS.set('#'); PRINTABLE_CHARS.set('$'); PRINTABLE_CHARS.set('%'); PRINTABLE_CHARS.set('&'); PRINTABLE_CHARS.set('\''); PRINTABLE_CHARS.set('('); PRINTABLE_CHARS.set(')'); PRINTABLE_CHARS.set('*'); PRINTABLE_CHARS.set('+'); PRINTABLE_CHARS.set(','); PRINTABLE_CHARS.set('-'); PRINTABLE_CHARS.set('.'); PRINTABLE_CHARS.set('/'); for (int i = '0'; i <= '9'; i++) { PRINTABLE_CHARS.set(i); } PRINTABLE_CHARS.set(':'); PRINTABLE_CHARS.set(';'); PRINTABLE_CHARS.set('<'); PRINTABLE_CHARS.set('>'); PRINTABLE_CHARS.set('@'); for (int i = 'A'; i <= 'Z'; i++) { PRINTABLE_CHARS.set(i); } PRINTABLE_CHARS.set('['); PRINTABLE_CHARS.set('\\'); PRINTABLE_CHARS.set(']'); PRINTABLE_CHARS.set('^'); PRINTABLE_CHARS.set('`'); for (int i = 'a'; i <= 'z'; i++) { PRINTABLE_CHARS.set(i); } PRINTABLE_CHARS.set('{'); PRINTABLE_CHARS.set('|'); PRINTABLE_CHARS.set('}'); PRINTABLE_CHARS.set('~'); } private static final byte BLANK = ' '; private static final byte UNDERSCORE = '_'; private static final byte ESCAPE_CHAR = '='; private static final byte CR = 13; private static final byte LF = 10; private boolean encodeBlanks = false; /** * Default constructor. */ public QCodec() { this(Charsets.UTF_8); } /** * Constructor which allows for the selection of a default charset. * * @param charset the default string charset to use. * @see Standard * charsets */ public QCodec(final Charset charset) { super(); this.charset = charset; } /** * Constructor which allows for the selection of a default charset. * * @param charsetName the charset to use. * @throws java.nio.charset.UnsupportedCharsetException If the named charset is unavailable * @see Standard * charsets */ public QCodec(final String charsetName) { this(Charset.forName(charsetName)); } @Override protected String getEncoding() { return "Q"; } @Override protected byte[] doEncoding(final byte[] bytes) { return encodeBytes(bytes, encodeBlanks); } @Override protected byte[] doDecoding(final byte[] bytes) throws DecoderException { return decodeQuotedPrintable(bytes); } /** * Encodes a string into its quoted-printable form using the specified charset. Unsafe * characters are escaped. * * @param str string to convert to quoted-printable form * @param charset the charset for str * @return quoted-printable string * @throws EncoderException thrown if a failure condition is encountered during the encoding * process. */ public String encode(final String str, final Charset charset) throws EncoderException { if (str == null) { return null; } return encodeText(str, charset); } /** * Encodes a string into its quoted-printable form using the specified charset. Unsafe * characters are escaped. * * @param str string to convert to quoted-printable form * @param charset the charset for str * @return quoted-printable string * @throws EncoderException thrown if a failure condition is encountered during the encoding * process. */ public String encode(final String str, final String charset) throws EncoderException { if (str == null) { return null; } try { return encodeText(str, charset); } catch (final UnsupportedEncodingException e) { throw new EncoderException(e.getMessage(), e); } } /** * Encodes a string into its quoted-printable form using the default charset. Unsafe characters * are escaped. * * @param str string to convert to quoted-printable form * @return quoted-printable string * @throws EncoderException thrown if a failure condition is encountered during the encoding * process. */ @Override public String encode(final String str) throws EncoderException { if (str == null) { return null; } return encode(str, getCharset()); } /** * Decodes a quoted-printable string into its original form. Escaped characters are converted * back to their original representation. * * @param str quoted-printable string to convert into its original form * @return original string * @throws DecoderException A decoder exception is thrown if a failure condition is encountered * during the decode process. */ @Override public String decode(final String str) throws DecoderException { if (str == null) { return null; } try { return decodeText(str); } catch (final UnsupportedEncodingException e) { throw new DecoderException(e.getMessage(), e); } } /** * Encodes an object into its quoted-printable form using the default charset. Unsafe characters * are escaped. * * @param obj object to convert to quoted-printable form * @return quoted-printable object * @throws EncoderException thrown if a failure condition is encountered during the encoding * process. */ @Override public Object encode(final Object obj) throws EncoderException { if (obj == null) { return null; } else if (obj instanceof String) { return encode((String)obj); } else { throw new EncoderException("Objects of type " + obj.getClass().getName() + " cannot be encoded using Q codec"); } } /** * Decodes a quoted-printable object into its original form. Escaped characters are converted * back to their original representation. * * @param obj quoted-printable object to convert into its original form * @return original object * @throws DecoderException Thrown if the argument is not a String. Thrown if a * failure condition is encountered during the decode process. */ @Override public Object decode(final Object obj) throws DecoderException { if (obj == null) { return null; } else if (obj instanceof String) { return decode((String)obj); } else { throw new DecoderException("Objects of type " + obj.getClass().getName() + " cannot be decoded using Q codec"); } } /** * Gets the default charset name used for string decoding and encoding. * * @return the default charset name */ public Charset getCharset() { return this.charset; } /** * Gets the default charset name used for string decoding and encoding. * * @return the default charset name */ public String getDefaultCharset() { return this.charset.name(); } /** * Tests if optional transformation of SPACE characters is to be used * * @return true if SPACE characters are to be transformed, false * otherwise */ public boolean isEncodeBlanks() { return this.encodeBlanks; } /** * Defines whether optional transformation of SPACE characters is to be used * * @param b true if SPACE characters are to be transformed, false * otherwise */ public void setEncodeBlanks(final boolean b) { this.encodeBlanks = b; } //------------------------------------------------------------- /** * @param b the bytes * @param encodingWord weather to encode word * @return the length of the encoded version of this byte array. */ public static int encodedLength(byte[] b, boolean encodingWord) { int len = 0; String specials = encodingWord ? WORD_SPECIALS : TEXT_SPECIALS; for (int i = 0; i < b.length; i++) { int c = b[i] & 0xff; // Mask off MSB if (c < 040 || c >= 0177 || specials.indexOf(c) >= 0) // needs encoding len += 3; // Q-encoding is 1 -> 3 conversion else len++; } return len; } /** * Decodes an array quoted-printable characters into an array of original bytes. Escaped * characters are converted back to their original representation. *

* This function fully implements the quoted-printable encoding specification (rule #1 through * rule #5) as defined in RFC 1521. * * @param bytes array of quoted-printable characters * @return array of original bytes * @throws DecoderException Thrown if quoted-printable decoding is unsuccessful */ public static final byte[] decodeQuotedPrintable(final byte[] bytes) throws DecoderException { if (bytes == null) { return null; } @SuppressWarnings("resource") final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int len = bytes.length; for (int i = 0; i < len; i++) { final int b = bytes[i]; if (b == UNDERSCORE) { buffer.write(BLANK); } else if (b == ESCAPE_CHAR) { if (i + 2 >= len) { throw new DecoderException("Invalid quoted printable encoding; truncated escape sequence"); } byte b1 = bytes[++i]; byte b2 = bytes[++i]; // we've found an encoded carriage return. The next char needs to be a newline if (b1 == CR) { if (b2 != LF) { throw new DecoderException("Invalid quoted printable encoding; CR must be followed by LF"); } // this was a soft linebreak inserted by the encoding. We just toss this away on decode. continue; } final int u = Utils.digit16(b1); final int l = Utils.digit16(b2); buffer.write((char)((u << 4) + l)); } else { buffer.write(b); } } return buffer.toByteArray(); } /** * Encodes an array of bytes using the defined encoding scheme. * * @param bytes Data to be encoded * @param encodeBlanks weather to encode blanks * @return A byte array containing the encoded data */ public static byte[] encodeBytes(byte[] bytes, boolean encodeBlanks) { if (bytes == null) { return null; } final byte[] data = QuotedPrintableCodec.encodeQuotedPrintable(PRINTABLE_CHARS, bytes); if (encodeBlanks) { for (int i = 0; i < data.length; i++) { if (data[i] == BLANK) { data[i] = UNDERSCORE; } } } return data; } /** * Decodes an array of bytes using the defined encoding scheme. * * @param bytes Data to be decoded * @return a byte array that contains decoded data * @throws DecoderException A decoder exception is thrown if a Decoder encounters a failure * condition during the decode process. */ public static byte[] decodeBytes(byte[] bytes) { return decodeQuotedPrintable(bytes); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy