panda.codec.net.RFC1522Codec Maven / Gradle / Ivy
package panda.codec.net;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import panda.codec.DecoderException;
import panda.codec.EncoderException;
import panda.lang.Strings;
/**
* Implements methods common to all codecs defined in RFC 1522.
*
* 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 immutable and thread-safe.
*
* @see MIME (Multipurpose Internet Mail Extensions)
* Part Two: Message Header Extensions for Non-ASCII Text
*/
abstract class RFC1522Codec {
/** Separator. */
protected static final char SEP = '?';
/** Prefix. */
protected static final String POSTFIX = "?=";
/** Postfix. */
protected static final String PREFIX = "=?";
/**
* Applies an RFC 1522 compliant encoding scheme to the given string of text with the given
* charset.
*
* This method constructs the "encoded-word" header common to all the RFC 1522 codecs and then
* invokes {@link #doEncoding(byte [])} method of a concrete class to perform the specific
* encoding.
*
* @param text a string to encode
* @param charset a charset to be used
* @return RFC 1522 compliant "encoded-word"
* @throws EncoderException thrown if there is an error condition during the Encoding process.
* @see Standard
* charsets
*/
protected String encodeText(final String text, final Charset charset) throws EncoderException {
if (text == null) {
return null;
}
final StringBuilder buffer = new StringBuilder();
buffer.append(PREFIX);
buffer.append(charset);
buffer.append(SEP);
buffer.append(this.getEncoding());
buffer.append(SEP);
final byte[] rawData = this.doEncoding(text.getBytes(charset));
buffer.append(Strings.newStringUsAscii(rawData));
buffer.append(POSTFIX);
return buffer.toString();
}
/**
* Applies an RFC 1522 compliant encoding scheme to the given string of text with the given
* charset.
*
* This method constructs the "encoded-word" header common to all the RFC 1522 codecs and then
* invokes {@link #doEncoding(byte [])} method of a concrete class to perform the specific
* encoding.
*
* @param text a string to encode
* @param charsetName the charset to use
* @return RFC 1522 compliant "encoded-word"
* @throws EncoderException thrown if there is an error condition during the Encoding process.
* @throws UnsupportedEncodingException if charset is not available
* @see Standard
* charsets
*/
protected String encodeText(final String text, final String charsetName) throws EncoderException,
UnsupportedEncodingException {
if (text == null) {
return null;
}
return this.encodeText(text, Charset.forName(charsetName));
}
/**
* Applies an RFC 1522 compliant decoding scheme to the given string of text.
*
* This method processes the "encoded-word" header common to all the RFC 1522 codecs and then
* invokes {@link #doEncoding(byte [])} method of a concrete class to perform the specific
* decoding.
*
* @param text a string to decode
* @return A new decoded String or null
if the input is null
.
* @throws DecoderException thrown if there is an error condition during the decoding process.
* @throws UnsupportedEncodingException thrown if charset specified in the "encoded-word" header
* is not supported
*/
protected String decodeText(final String text) throws DecoderException, UnsupportedEncodingException {
if (text == null) {
return null;
}
if (!text.startsWith(PREFIX) || !text.endsWith(POSTFIX)) {
throw new DecoderException("RFC 1522 violation: malformed encoded content");
}
final int terminator = text.length() - 2;
int from = 2;
int to = text.indexOf(SEP, from);
if (to == terminator) {
throw new DecoderException("RFC 1522 violation: charset token not found");
}
final String charset = text.substring(from, to);
if (charset.equals("")) {
throw new DecoderException("RFC 1522 violation: charset not specified");
}
from = to + 1;
to = text.indexOf(SEP, from);
if (to == terminator) {
throw new DecoderException("RFC 1522 violation: encoding token not found");
}
final String encoding = text.substring(from, to);
if (!getEncoding().equalsIgnoreCase(encoding)) {
throw new DecoderException("This codec cannot decode " + encoding + " encoded content");
}
from = to + 1;
to = text.indexOf(SEP, from);
byte[] data = Strings.getBytesUsAscii(text.substring(from, to));
data = doDecoding(data);
return new String(data, charset);
}
/**
* Returns the codec name (referred to as encoding in the RFC 1522).
*
* @return name of the codec
*/
protected abstract String getEncoding();
/**
* Encodes an array of bytes using the defined encoding scheme.
*
* @param bytes Data to be encoded
* @return A byte array containing the encoded data
* @throws EncoderException thrown if the Encoder encounters a failure condition during the
* encoding process.
*/
protected abstract byte[] doEncoding(byte[] bytes) throws EncoderException;
/**
* 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.
*/
protected abstract byte[] doDecoding(byte[] bytes) throws DecoderException;
}