/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you 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.
*/
/*
* Written by Robert Harder and released to the public domain, as explained at
* http://creativecommons.org/licenses/publicdomain
*/
package io.netty.handler.codec.base64;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.ByteProcessor;
import io.netty.util.internal.PlatformDependent;
import java.nio.ByteOrder;
Utility class for ByteBuf
that encodes and decodes to and from Base64 notation.
The encoding and decoding algorithm in this class has been derived from
Robert Harder's Public Domain
Base64 Encoder/Decoder.
/**
* Utility class for {@link ByteBuf} that encodes and decodes to and from
* <a href="http://en.wikipedia.org/wiki/Base64">Base64</a> notation.
* <p>
* The encoding and decoding algorithm in this class has been derived from
* <a href="http://iharder.sourceforge.net/current/java/base64/">Robert Harder's Public Domain
* Base64 Encoder/Decoder</a>.
*/
public final class Base64 {
Maximum line length (76) of Base64 output. /** Maximum line length (76) of Base64 output. */
private static final int MAX_LINE_LENGTH = 76;
The equals sign (=) as a byte. /** The equals sign (=) as a byte. */
private static final byte EQUALS_SIGN = (byte) '=';
The new line character (\n) as a byte. /** The new line character (\n) as a byte. */
private static final byte NEW_LINE = (byte) '\n';
private static final byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
private static final byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
private static byte[] alphabet(Base64Dialect dialect) {
if (dialect == null) {
throw new NullPointerException("dialect");
}
return dialect.alphabet;
}
private static byte[] decodabet(Base64Dialect dialect) {
if (dialect == null) {
throw new NullPointerException("dialect");
}
return dialect.decodabet;
}
private static boolean breakLines(Base64Dialect dialect) {
if (dialect == null) {
throw new NullPointerException("dialect");
}
return dialect.breakLinesByDefault;
}
public static ByteBuf encode(ByteBuf src) {
return encode(src, Base64Dialect.STANDARD);
}
public static ByteBuf encode(ByteBuf src, Base64Dialect dialect) {
return encode(src, breakLines(dialect), dialect);
}
public static ByteBuf encode(ByteBuf src, boolean breakLines) {
return encode(src, breakLines, Base64Dialect.STANDARD);
}
public static ByteBuf encode(ByteBuf src, boolean breakLines, Base64Dialect dialect) {
if (src == null) {
throw new NullPointerException("src");
}
ByteBuf dest = encode(src, src.readerIndex(), src.readableBytes(), breakLines, dialect);
src.readerIndex(src.writerIndex());
return dest;
}
public static ByteBuf encode(ByteBuf src, int off, int len) {
return encode(src, off, len, Base64Dialect.STANDARD);
}
public static ByteBuf encode(ByteBuf src, int off, int len, Base64Dialect dialect) {
return encode(src, off, len, breakLines(dialect), dialect);
}
public static ByteBuf encode(
ByteBuf src, int off, int len, boolean breakLines) {
return encode(src, off, len, breakLines, Base64Dialect.STANDARD);
}
public static ByteBuf encode(
ByteBuf src, int off, int len, boolean breakLines, Base64Dialect dialect) {
return encode(src, off, len, breakLines, dialect, src.alloc());
}
public static ByteBuf encode(
ByteBuf src, int off, int len, boolean breakLines, Base64Dialect dialect, ByteBufAllocator allocator) {
if (src == null) {
throw new NullPointerException("src");
}
if (dialect == null) {
throw new NullPointerException("dialect");
}
ByteBuf dest = allocator.buffer(encodedBufferSize(len, breakLines)).order(src.order());
byte[] alphabet = alphabet(dialect);
int d = 0;
int e = 0;
int len2 = len - 2;
int lineLength = 0;
for (; d < len2; d += 3, e += 4) {
encode3to4(src, d + off, 3, dest, e, alphabet);
lineLength += 4;
if (breakLines && lineLength == MAX_LINE_LENGTH) {
dest.setByte(e + 4, NEW_LINE);
e ++;
lineLength = 0;
} // end if: end of line
} // end for: each piece of array
if (d < len) {
encode3to4(src, d + off, len - d, dest, e, alphabet);
e += 4;
} // end if: some padding needed
// Remove last byte if it's a newline
if (e > 1 && dest.getByte(e - 1) == NEW_LINE) {
e--;
}
return dest.slice(0, e);
}
private static void encode3to4(
ByteBuf src, int srcOffset, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) {
// 1 2 3
// 01234567890123456789012345678901 Bit position
// --------000000001111111122222222 Array position from threeBytes
// --------| || || || | Six bit groups to index ALPHABET
// >>18 >>12 >> 6 >> 0 Right shift necessary
// 0x3f 0x3f 0x3f Additional AND
// Create buffer with zero-padding if there are only one or two
// significant bytes passed in the array.
// We have to shift left 24 in order to flush out the 1's that appear
// when Java treats a value as negative that is cast from a byte to an int.
if (src.order() == ByteOrder.BIG_ENDIAN) {
final int inBuff;
switch (numSigBytes) {
case 1:
inBuff = toInt(src.getByte(srcOffset));
break;
case 2:
inBuff = toIntBE(src.getShort(srcOffset));
break;
default:
inBuff = numSigBytes <= 0 ? 0 : toIntBE(src.getMedium(srcOffset));
break;
}
encode3to4BigEndian(inBuff, numSigBytes, dest, destOffset, alphabet);
} else {
final int inBuff;
switch (numSigBytes) {
case 1:
inBuff = toInt(src.getByte(srcOffset));
break;
case 2:
inBuff = toIntLE(src.getShort(srcOffset));
break;
default:
inBuff = numSigBytes <= 0 ? 0 : toIntLE(src.getMedium(srcOffset));
break;
}
encode3to4LittleEndian(inBuff, numSigBytes, dest, destOffset, alphabet);
}
}
// package-private for testing
static int encodedBufferSize(int len, boolean breakLines) {
// Cast len to long to prevent overflow
long len43 = ((long) len << 2) / 3;
// Account for padding
long ret = (len43 + 3) & ~3;
if (breakLines) {
ret += len43 / MAX_LINE_LENGTH;
}
return ret < Integer.MAX_VALUE ? (int) ret : Integer.MAX_VALUE;
}
private static int toInt(byte value) {
return (value & 0xff) << 16;
}
private static int toIntBE(short value) {
return (value & 0xff00) << 8 | (value & 0xff) << 8;
}
private static int toIntLE(short value) {
return (value & 0xff) << 16 | (value & 0xff00);
}
private static int toIntBE(int mediumValue) {
return (mediumValue & 0xff0000) | (mediumValue & 0xff00) | (mediumValue & 0xff);
}
private static int toIntLE(int mediumValue) {
return (mediumValue & 0xff) << 16 | (mediumValue & 0xff00) | (mediumValue & 0xff0000) >>> 16;
}
private static void encode3to4BigEndian(
int inBuff, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) {
// Packing bytes into an int to reduce bound and reference count checking.
switch (numSigBytes) {
case 3:
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 |
alphabet[inBuff >>> 12 & 0x3f] << 16 |
alphabet[inBuff >>> 6 & 0x3f] << 8 |
alphabet[inBuff & 0x3f]);
break;
case 2:
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 |
alphabet[inBuff >>> 12 & 0x3f] << 16 |
alphabet[inBuff >>> 6 & 0x3f] << 8 |
EQUALS_SIGN);
break;
case 1:
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 |
alphabet[inBuff >>> 12 & 0x3f] << 16 |
EQUALS_SIGN << 8 |
EQUALS_SIGN);
break;
default:
// NOOP
break;
}
}
private static void encode3to4LittleEndian(
int inBuff, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) {
// Packing bytes into an int to reduce bound and reference count checking.
switch (numSigBytes) {
case 3:
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] |
alphabet[inBuff >>> 12 & 0x3f] << 8 |
alphabet[inBuff >>> 6 & 0x3f] << 16 |
alphabet[inBuff & 0x3f] << 24);
break;
case 2:
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] |
alphabet[inBuff >>> 12 & 0x3f] << 8 |
alphabet[inBuff >>> 6 & 0x3f] << 16 |
EQUALS_SIGN << 24);
break;
case 1:
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] |
alphabet[inBuff >>> 12 & 0x3f] << 8 |
EQUALS_SIGN << 16 |
EQUALS_SIGN << 24);
break;
default:
// NOOP
break;
}
}
public static ByteBuf decode(ByteBuf src) {
return decode(src, Base64Dialect.STANDARD);
}
public static ByteBuf decode(ByteBuf src, Base64Dialect dialect) {
if (src == null) {
throw new NullPointerException("src");
}
ByteBuf dest = decode(src, src.readerIndex(), src.readableBytes(), dialect);
src.readerIndex(src.writerIndex());
return dest;
}
public static ByteBuf decode(
ByteBuf src, int off, int len) {
return decode(src, off, len, Base64Dialect.STANDARD);
}
public static ByteBuf decode(
ByteBuf src, int off, int len, Base64Dialect dialect) {
return decode(src, off, len, dialect, src.alloc());
}
public static ByteBuf decode(
ByteBuf src, int off, int len, Base64Dialect dialect, ByteBufAllocator allocator) {
if (src == null) {
throw new NullPointerException("src");
}
if (dialect == null) {
throw new NullPointerException("dialect");
}
// Using a ByteProcessor to reduce bound and reference count checking.
return new Decoder().decode(src, off, len, allocator, dialect);
}
// package-private for testing
static int decodedBufferSize(int len) {
return len - (len >>> 2);
}
private static final class Decoder implements ByteProcessor {
private final byte[] b4 = new byte[4];
private int b4Posn;
private byte sbiCrop;
private byte sbiDecode;
private byte[] decodabet;
private int outBuffPosn;
private ByteBuf dest;
ByteBuf decode(ByteBuf src, int off, int len, ByteBufAllocator allocator, Base64Dialect dialect) {
dest = allocator.buffer(decodedBufferSize(len)).order(src.order()); // Upper limit on size of output
decodabet = decodabet(dialect);
try {
src.forEachByte(off, len, this);
return dest.slice(0, outBuffPosn);
} catch (Throwable cause) {
dest.release();
PlatformDependent.throwException(cause);
return null;
}
}
@Override
public boolean process(byte value) throws Exception {
sbiCrop = (byte) (value & 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) { // Equals sign or better
b4[b4Posn ++] = sbiCrop;
if (b4Posn > 3) { // Quartet built
outBuffPosn += decode4to3(b4, dest, outBuffPosn, decodabet);
b4Posn = 0;
// If that was the equals sign, break out of 'for' loop
if (sbiCrop == EQUALS_SIGN) {
return false;
}
}
}
return true;
}
throw new IllegalArgumentException(
"invalid bad Base64 input character: " + (short) (value & 0xFF) + " (decimal)");
}
private static int decode4to3(byte[] src, ByteBuf dest, int destOffset, byte[] decodabet) {
final byte src0 = src[0];
final byte src1 = src[1];
final byte src2 = src[2];
final int decodedValue;
if (src2 == EQUALS_SIGN) {
// Example: Dk==
try {
decodedValue = (decodabet[src0] & 0xff) << 2 | (decodabet[src1] & 0xff) >>> 4;
} catch (IndexOutOfBoundsException ignored) {
throw new IllegalArgumentException("not encoded in Base64");
}
dest.setByte(destOffset, decodedValue);
return 1;
}
final byte src3 = src[3];
if (src3 == EQUALS_SIGN) {
// Example: DkL=
final byte b1 = decodabet[src1];
// Packing bytes into a short to reduce bound and reference count checking.
try {
if (dest.order() == ByteOrder.BIG_ENDIAN) {
// The decodabet bytes are meant to straddle byte boundaries and so we must carefully mask out
// the bits we care about.
decodedValue = ((decodabet[src0] & 0x3f) << 2 | (b1 & 0xf0) >> 4) << 8 |
(b1 & 0xf) << 4 | (decodabet[src2] & 0xfc) >>> 2;
} else {
// This is just a simple byte swap of the operation above.
decodedValue = (decodabet[src0] & 0x3f) << 2 | (b1 & 0xf0) >> 4 |
((b1 & 0xf) << 4 | (decodabet[src2] & 0xfc) >>> 2) << 8;
}
} catch (IndexOutOfBoundsException ignored) {
throw new IllegalArgumentException("not encoded in Base64");
}
dest.setShort(destOffset, decodedValue);
return 2;
}
// Example: DkLE
try {
if (dest.order() == ByteOrder.BIG_ENDIAN) {
decodedValue = (decodabet[src0] & 0x3f) << 18 |
(decodabet[src1] & 0xff) << 12 |
(decodabet[src2] & 0xff) << 6 |
decodabet[src3] & 0xff;
} else {
final byte b1 = decodabet[src1];
final byte b2 = decodabet[src2];
// The goal is to byte swap the BIG_ENDIAN case above. There are 2 interesting things to consider:
// 1. We are byte swapping a 3 byte data type. The left and the right byte switch, but the middle
// remains the same.
// 2. The contents straddles byte boundaries. This means bytes will be pulled apart during the byte
// swapping process.
decodedValue = (decodabet[src0] & 0x3f) << 2 |
// The bottom half of b1 remains in the middle.
(b1 & 0xf) << 12 |
// The top half of b1 are the least significant bits after the swap.
(b1 & 0xf0) >>> 4 |
// The bottom 2 bits of b2 will be the most significant bits after the swap.
(b2 & 0x3) << 22 |
// The remaining 6 bits of b2 remain in the middle.
(b2 & 0xfc) << 6 |
(decodabet[src3] & 0xff) << 16;
}
} catch (IndexOutOfBoundsException ignored) {
throw new IllegalArgumentException("not encoded in Base64");
}
dest.setMedium(destOffset, decodedValue);
return 3;
}
}
private Base64() {
// Unused
}
}