/*
* Copyright (C) 2004-2013 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* The source is based on the work of Robert Harder.
*
* I am placing this code in the Public Domain.
* Do with it as you will.
* This software comes with no guarantees or warranties but with plenty of well-wishing instead!
* Please visit http://iharder.net/base64
* periodically to check for updates or to contribute improvements.
*
* Valid options: * *
* GZIP: gzip-compresses object before encoding it. * DONT_BREAK_LINES: don't break lines at 76 characters * <i>Note: Technically, this makes your encoding non-compliant.</i> **
* Example: encodeObject( myObj, Base64.GZIP )
or
*
* Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )
* @param serializableObject The object to encode
* @param options
* @options Specified options
* @return The Base64-encoded object
* @see Base64#GZIP
* @see Base64#DONT_BREAK_LINES
* @since 2.0
*/
public static String encodeObject(Serializable serializableObject, int options)
{
// Isolate options
int gzip = (options & GZIP);
int dontBreakLines = (options & DONT_BREAK_LINES);
// ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
byte[] value = null;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
Base64.OutputStream b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(b64os);
FilterOutputStream os = (gzip == GZIP) ? gzipOutputStream : b64os;
ObjectOutputStream oos = new ObjectOutputStream(os))
{
oos.writeObject(serializableObject);
value = baos.toByteArray();
}
catch (IOException e)
{
e.printStackTrace();
return null;
}
return value != null ? new String(value, PREFERRED_ENCODING) : null;
}
/**
* Encodes a byte array into Base64 notation. Does not GZip-compress data.
* @param source The data to convert
* @return
* @since 1.4
*/
public static String encodeBytes(byte[] source)
{
return encodeBytes(source, 0, source.length, NO_OPTIONS);
}
/**
* Encodes a byte array into Base64 notation.
*
* Valid options: * *
* GZIP: gzip-compresses object before encoding it. * DONT_BREAK_LINES: don't break lines at 76 characters * <i>Note: Technically, this makes your encoding non-compliant.</i> **
* Example: encodeBytes( myData, Base64.GZIP )
or
*
* Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )
* @param source The data to convert
* @param options Specified options
* @return
* @see Base64#GZIP
* @see Base64#DONT_BREAK_LINES
* @since 2.0
*/
public static String encodeBytes(byte[] source, int options)
{
return encodeBytes(source, 0, source.length, options);
}
/**
* Encodes a byte array into Base64 notation. Does not GZip-compress data.
* @param source The data to convert
* @param off Offset in array where conversion should begin
* @param len Length of data to convert
* @return
* @since 1.4
*/
public static String encodeBytes(byte[] source, int off, int len)
{
return encodeBytes(source, off, len, NO_OPTIONS);
}
/**
* Encodes a byte array into Base64 notation.
*
* Valid options: * *
* GZIP: gzip-compresses object before encoding it. * DONT_BREAK_LINES: don't break lines at 76 characters * <i>Note: Technically, this makes your encoding non-compliant.</i> **
* Example: encodeBytes( myData, Base64.GZIP )
or
*
* Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )
* @param source The data to convert
* @param off Offset in array where conversion should begin
* @param len Length of data to convert
* @param options Specified options
* @return
* @see Base64#GZIP
* @see Base64#DONT_BREAK_LINES
* @since 2.0
*/
public static String encodeBytes(byte[] source, int off, int len, int options)
{
// Isolate options
int dontBreakLines = (options & DONT_BREAK_LINES);
int gzip = (options & GZIP);
// Compress?
if (gzip == GZIP)
{
// GZip -> Base64 -> ByteArray
byte[] value = null;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
Base64.OutputStream b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines);
GZIPOutputStream gzos = new GZIPOutputStream(b64os))
{
gzos.write(source, off, len);
value = baos.toByteArray();
}
catch (IOException e)
{
_log.warning("Base64: " + e.getMessage());
return null;
}
// Return value according to relevant encoding.
if (value != null)
{
return new String(value, PREFERRED_ENCODING);
}
}
// Convert option to boolean in way that code likes it.
boolean breakLines = dontBreakLines == 0;
int len43 = (len * 4) / 3;
byte[] outBuff = new byte[(len43) // Main 4:3
+ ((len % 3) > 0 ? 4 : 0) // Account for padding
+ (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)]; // New lines
int d = 0;
int e = 0;
int len2 = len - 2;
int lineLength = 0;
for (; d < len2; d += 3, e += 4)
{
encode3to4(source, d + off, 3, outBuff, e);
lineLength += 4;
if (breakLines && (lineLength == MAX_LINE_LENGTH))
{
outBuff[e + 4] = NEW_LINE;
e++;
lineLength = 0;
}
} // en dfor: each piece of array
if (d < len)
{
encode3to4(source, d + off, len - d, outBuff, e);
e += 4;
}
// Return value according to relevant encoding.
return new String(outBuff, 0, e, PREFERRED_ENCODING);
}
/* D E C O D I N G M E T H O D S */
/**
* Decodes four bytes from array source and writes the resulting bytes (up to three of them) to destination. The source and destination arrays can be manipulated anywhere along their length by specifying srcOffset and destOffset. This method does not
* check to make sure your arrays are large enough to accomodate srcOffset + 4 for the source array or destOffset + 3 for the destination array. This method returns the actual number of bytes that were converted from the Base64 encoding.
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @return the number of decoded bytes converted
* @since 1.3
*/
static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset)
{
// Example: Dk==
if (source[srcOffset + 2] == EQUALS_SIGN)
{
// Two ways to do the same thing. Don't know which way I like best.
// int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6
// )
// | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12);
destination[destOffset] = (byte) (outBuff >>> 16);
return 1;
}
// Example: DkL=
else if (source[srcOffset + 3] == EQUALS_SIGN)
{
// Two ways to do the same thing. Don't know which way I like best.
// int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6
// )
// | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
// | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6);
destination[destOffset] = (byte) (outBuff >>> 16);
destination[destOffset + 1] = (byte) (outBuff >>> 8);
return 2;
}
// Example: DkLE
else
{
try
{
// Two ways to do the same thing. Don't know which way I like
// best.
// int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 )
// >>> 6 )
// | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
// | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
// | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) | ((DECODABET[source[srcOffset + 3]] & 0xFF));
destination[destOffset] = (byte) (outBuff >> 16);
destination[destOffset + 1] = (byte) (outBuff >> 8);
destination[destOffset + 2] = (byte) (outBuff);
return 3;
}
catch (Exception e)
{
System.out.println(StringUtil.concat(String.valueOf(source[srcOffset]), ": ", String.valueOf(DECODABET[source[srcOffset]])));
System.out.println(StringUtil.concat(String.valueOf(source[srcOffset + 1]), ": ", String.valueOf(DECODABET[source[srcOffset + 1]])));
System.out.println(StringUtil.concat(String.valueOf(source[srcOffset + 2]), ": ", String.valueOf(DECODABET[source[srcOffset + 2]])));
System.out.println(StringUtil.concat(String.valueOf(source[srcOffset + 3]), ": ", String.valueOf(DECODABET[source[srcOffset + 3]])));
return -1;
}
}
}
/**
* Very low-level access to decoding ASCII characters in the form of a byte array. Does not support automatically gunzipping or any other "fancy" features.
* @param source The Base64 encoded data
* @param off The offset of where to begin decoding
* @param len The length of characters to decode
* @return decoded data
* @since 1.3
*/
public static byte[] decode(byte[] source, int off, int len)
{
int len34 = (len * 3) / 4;
byte[] outBuff = new byte[len34]; // Upper limit on size of output
int outBuffPosn = 0;
byte[] b4 = new byte[4];
int b4Posn = 0;
int i = 0;
byte sbiCrop = 0;
byte sbiDecode = 0;
for (i = off; i < (off + len); i++)
{
sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits
sbiDecode = DECODABET[sbiCrop];
if (sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or better
{
if (sbiDecode >= EQUALS_SIGN_ENC)
{
b4[b4Posn++] = sbiCrop;
if (b4Posn > 3)
{
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn);
b4Posn = 0;
// If that was the equals sign, break out of 'for' loop
if (sbiCrop == EQUALS_SIGN)
{
break;
}
} // end if: quartet built
} // end if: equals sign or better
} // end if: white space, equals sign or better
else
{
System.err.println(StringUtil.concat("Bad Base64 input character at ", String.valueOf(i), ": ", String.valueOf(source[i]), "(decimal)"));
return null;
}
}
byte[] out = new byte[outBuffPosn];
System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
return out;
}
/**
* Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it.
* @param s the string to decode
* @return the decoded data
* @since 1.4
*/
public static byte[] decode(String s)
{
byte[] bytes = s.getBytes(PREFERRED_ENCODING);
// Decode
bytes = decode(bytes, 0, bytes.length);
// Check to see if it's gzip-compressed
// GZIP Magic Two-Byte Number: 0x8b1f (35615)
// In case decoding returned null
if ((bytes != null) && (bytes.length >= 2))
{
final int head = (bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
// Don't want to get ArrayIndexOutOfBounds exception
if ((bytes.length >= 4) && (GZIPInputStream.GZIP_MAGIC == head))
{
byte[] buffer = new byte[2048];
int length = 0;
try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
GZIPInputStream gzis = new GZIPInputStream(bais);
ByteArrayOutputStream baos = new ByteArrayOutputStream())
{
while ((length = gzis.read(buffer)) >= 0)
{
baos.write(buffer, 0, length);
}
// No error? Get new bytes.
bytes = baos.toByteArray();
}
catch (IOException e)
{
// Just return originally-decoded bytes
}
}
}
return bytes;
}
/**
* Attempts to decode Base64 data and deserialize a Java Object within. Returns null if there was an error.
* @param encodedObject The Base64 data to decode
* @return The decoded and deserialized object
* @since 1.5
*/
public static Object decodeToObject(String encodedObject)
{
// Decode and gunzip if necessary
byte[] objBytes = decode(encodedObject);
Object obj = null;
try (ByteArrayInputStream bais = new ByteArrayInputStream(objBytes);
ObjectInputStream ois = new ObjectInputStream(bais))
{
obj = ois.readObject();
}
catch (IOException e)
{
_log.warning("Base64: " + e.getMessage());
}
catch (ClassNotFoundException e)
{
_log.warning("Base64: " + e.getMessage());
}
return obj;
}
/* I N N E R C L A S S I N P U T S T R E A M */
/**
* A {@link #InputStream} will read data from another {@link java.io.InputStream}, given in the constructor, and encode/decode to/from Base64 notation on the fly.
* @see Base64
* @see java.io.FilterInputStream
* @since 1.3
*/
public static class InputStream extends FilterInputStream
{
// private int options; // Options specified
private final boolean encode; // Encoding or decoding
private int position; // Current position in the buffer
private final byte[] buffer; // Small buffer holding converted data
private final int bufferLength; // Length of buffer (3 or 4)
private int numSigBytes; // Number of meaningful bytes in the buffer
private int lineLength;
private final boolean breakLines; // Break lines at less than 80 characters
/**
* Constructs a {@link #InputStream} in DECODE mode.
* @param pIn the {@link java.io.InputStream} from which to read data.
* @since 1.3
*/
public InputStream(java.io.InputStream pIn)
{
this(pIn, DECODE);
}
/**
* Constructs a {@link #InputStream} in either ENCODE or DECODE mode.
*
* Valid options: * *
* ENCODE or DECODE: Encode or Decode as data is read. * DONT_BREAK_LINES: don't break lines at 76 characters * (only meaningful when encoding) * <i>Note: Technically, this makes your encoding non-compliant.</i> **
* Example: new Base64.InputStream( in, Base64.DECODE )
* @param pIn the {@link java.io.InputStream} from which to read data.
* @param options Specified options
* @see Base64#ENCODE
* @see Base64#DECODE
* @see Base64#DONT_BREAK_LINES
* @since 2.0
*/
public InputStream(java.io.InputStream pIn, int options)
{
super(pIn);
// this.options = options;
breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
encode = (options & ENCODE) == ENCODE;
bufferLength = encode ? 4 : 3;
buffer = new byte[bufferLength];
position = -1;
lineLength = 0;
}
/**
* Reads enough of the input stream to convert to/from Base64 and returns the next byte.
* @return next byte
* @since 1.3
*/
@Override
public int read() throws IOException
{
// Do we need to get data?
if (position < 0)
{
if (encode)
{
byte[] b3 = new byte[3];
int numBinaryBytes = 0;
for (int i = 0; i < 3; i++)
{
try
{
int b = in.read();
// If end of stream, b is -1.
if (b >= 0)
{
b3[i] = (byte) b;
numBinaryBytes++;
}
}
catch (IOException e)
{
// Only a problem if we got no data at all.
if (i == 0)
{
throw e;
}
}
}
if (numBinaryBytes > 0)
{
encode3to4(b3, 0, numBinaryBytes, buffer, 0);
position = 0;
numSigBytes = 4;
}
else
{
return -1;
}
}
else
{
byte[] b4 = new byte[4];
int i = 0;
for (i = 0; i < 4; i++)
{
// Read four "meaningful" bytes:
int b = 0;
do
{
b = in.read();
}
while ((b >= 0) && (DECODABET[b & 0x7f] <= WHITE_SPACE_ENC));
if (b < 0)
{
break; // Reads a -1 if end of stream
}
b4[i] = (byte) b;
} // end for: each needed input byte
if (i == 4)
{
numSigBytes = decode4to3(b4, 0, buffer, 0);
position = 0;
} // end if: got four characters
else if (i == 0)
{
return -1;
} // end else if: also padded correctly
else
{
// Must have broken out from above.
throw new IOException("Improperly padded Base64 input.");
}
}
}
// Got data?
if (position >= 0)
{
// End of relevant data?
if ( /* !encode && */position >= numSigBytes)
{
return -1;
}
if (encode && breakLines && (lineLength >= MAX_LINE_LENGTH))
{
lineLength = 0;
return NEW_LINE;
}
// This isn't important when decoding but throwing an extra "if" seems just as wasteful.
lineLength++;
int b = buffer[position++];
if (position >= bufferLength)
{
position = -1;
}
// This is how you "cast" a byte that's intended to be unsigned.
return b & 0xFF;
}
// When JDK1.4 is more accepted, use an assertion here.
throw new IOException("Error in Base64 code reading stream.");
}
/**
* Calls {@link #read} repeatedly until the end of stream is reached or len bytes are read. Returns number of bytes read into array or -1 if end of stream is encountered.
* @param dest array to hold values
* @param off offset for array
* @param len max number of bytes to read into array
* @return bytes read into array or -1 if end of stream is encountered.
* @since 1.3
*/
@Override
public int read(byte[] dest, int off, int len) throws IOException
{
int i;
int b;
for (i = 0; i < len; i++)
{
b = read();
// if( b < 0 && i == 0 )
// return -1;
if (b >= 0)
{
dest[off + i] = (byte) b;
}
else if (i == 0)
{
return -1;
}
else
{
break;
}
}
return i;
}
}
/* I N N E R C L A S S O U T P U T S T R E A M */
/**
* A {@link #OutputStream} will write data to another {@link java.io.OutputStream}, given in the constructor, and encode/decode to/from Base64 notation on the fly.
* @see Base64
* @see java.io.FilterOutputStream
* @since 1.3
*/
public static class OutputStream extends FilterOutputStream
{
// private int options;
private final boolean encode;
private int position;
private byte[] buffer;
private final int bufferLength;
private int lineLength;
private final boolean breakLines;
private final byte[] b4; // Scratch used in a few places
private boolean suspendEncoding;
/**
* Constructs a {@link #OutputStream} in ENCODE mode.
* @param pOut the {@link java.io.OutputStream} to which data will be written.
* @since 1.3
*/
public OutputStream(java.io.OutputStream pOut)
{
this(pOut, ENCODE);
}
/**
* Constructs a {@link #OutputStream} in either ENCODE or DECODE mode.
*
* Valid options: * *
* ENCODE or DECODE: Encode or Decode as data is read. * DONT_BREAK_LINES: don't break lines at 76 characters * (only meaningful when encoding) * <i>Note: Technically, this makes your encoding non-compliant.</i> **
* Example: new Base64.OutputStream( out, Base64.ENCODE )
* @param pOut the {@link java.io.OutputStream} to which data will be written.
* @param options Specified options.
* @see Base64#ENCODE
* @see Base64#DECODE
* @see Base64#DONT_BREAK_LINES
* @since 1.3
*/
public OutputStream(java.io.OutputStream pOut, int options)
{
super(pOut);
// this.options = options;
breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
encode = (options & ENCODE) == ENCODE;
bufferLength = encode ? 3 : 4;
buffer = new byte[bufferLength];
position = 0;
lineLength = 0;
suspendEncoding = false;
b4 = new byte[4];
}
/**
* Writes the byte to the output stream after converting to/from Base64 notation. When encoding, bytes are buffered three at a time before the output stream actually gets a write() call. When decoding, bytes are buffered four at a time.
* @param theByte the byte to write
* @since 1.3
*/
@Override
public void write(int theByte) throws IOException
{
// Encoding suspended?
if (suspendEncoding)
{
super.out.write(theByte);
return;
}
// Encode?
if (encode)
{
buffer[position++] = (byte) theByte;
if (position >= bufferLength) // Enough to encode.
{
out.write(encode3to4(b4, buffer, bufferLength));
lineLength += 4;
if (breakLines && (lineLength >= MAX_LINE_LENGTH))
{
out.write(NEW_LINE);
lineLength = 0;
}
position = 0;
}
}
else
{
// Meaningful Base64 character?
if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC)
{
buffer[position++] = (byte) theByte;
if (position >= bufferLength) // Enough to output.
{
int len = Base64.decode4to3(buffer, 0, b4, 0);
out.write(b4, 0, len);
// out.write( Base64.decode4to3( buffer ) );
position = 0;
}
}
else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC)
{
throw new IOException("Invalid character in Base64 data.");
}
}
}
/**
* Calls {@link #write} repeatedly until len bytes are written.
* @param theBytes array from which to read bytes
* @param off offset for array
* @param len max number of bytes to read into array
* @since 1.3
*/
@Override
public void write(byte[] theBytes, int off, int len) throws IOException
{
// Encoding suspended?
if (suspendEncoding)
{
super.out.write(theBytes, off, len);
return;
}
for (int i = 0; i < len; i++)
{
write(theBytes[off + i]);
}
}
/**
* Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without closing the stream.
* @throws IOException
*/
public void flushBase64() throws IOException
{
if (position > 0)
{
if (encode)
{
out.write(encode3to4(b4, buffer, position));
position = 0;
}
else
{
throw new IOException("Base64 input not properly padded.");
}
}
}
/**
* Flushes and closes (I think, in the superclass) the stream.
* @since 1.3
*/
@Override
public void close() throws IOException
{
// 1. Ensure that pending characters are written
flushBase64();
// 2. Actually close the stream
// Base class both flushes and closes.
super.close();
buffer = null;
out = null;
}
/**
* Suspends encoding of the stream. May be helpful if you need to embed a piece of base640-encoded data in a stream.
* @throws IOException
* @since 1.5.1
*/
public void suspendEncoding() throws IOException
{
flushBase64();
suspendEncoding = true;
}
/**
* Resumes encoding of the stream. May be helpful if you need to embed a piece of base640-encoded data in a stream.
* @since 1.5.1
*/
public void resumeEncoding()
{
suspendEncoding = false;
}
}
}