com.dyuproject.protostuff.me.B64Code Maven / Gradle / Ivy
//========================================================================
//Copyright 2007-2010 David Yu [email protected]
//------------------------------------------------------------------------
//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 com.dyuproject.protostuff.me;
//========================================================================
//Copyright 1999-2005 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//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.
//========================================================================
import java.io.IOException;
import java.io.OutputStream;
/* ------------- Ported from org.mortbay.jetty.security.B64Code ------------- */
/** Fast B64 Encoder/Decoder as described in RFC 1421.
* Does not insert or interpret whitespace as described in RFC
* 1521. If you require this you must pre/post process your data.
*
Note that in a web context the usual case is to not want
* linebreaks or other white space in the encoded output.
*
* @author Brett Sealey (bretts)
* @author Greg Wilkins (gregw)
* @author David Yu (dyu)
*/
public final class B64Code
{
// ------------------------------------------------------------------
static final byte pad = (byte)'=';
static final byte[] nibble2code=
{
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
};
static final byte[] code2nibble;
static
{
code2nibble=new byte[256];
for (int i=0;i<256;i++)
code2nibble[i]=-1;
for (byte b=0;b<64;b++)
code2nibble[nibble2code[b]]=b;
code2nibble[pad]=0;
}
private B64Code() {}
/**
* Fast Base 64 encode as described in RFC 1421.
*/
public static byte[] encode(byte[] input)
{
return encode(input, 0, input.length);
}
/**
* Fast Base 64 encode as described in RFC 1421.
*/
public static byte[] encode(byte[] input, int inOffset, int inLen)
{
final byte[] output = new byte[((inLen+2)/3)*4];
encode(input, inOffset, inLen, output , 0);
return output;
}
// ------------------------------------------------------------------
/**
* Fast Base 64 encode as described in RFC 1421.
*
Does not insert whitespace as described in RFC 1521.
*
Avoids creating extra copies of the input/output.
*/
private static void encode(final byte[] input, int inOffset, final int inLen,
final byte[] output, int outOffset)
{
byte b0, b1, b2;
final int remaining = inLen%3, stop = inOffset + (inLen-remaining);
while (inOffset < stop)
{
b0=input[inOffset++];
b1=input[inOffset++];
b2=input[inOffset++];
output[outOffset++]=nibble2code[(b0>>>2)&0x3f];
output[outOffset++]=nibble2code[(b0<<4)&0x3f|(b1>>>4)&0x0f];
output[outOffset++]=nibble2code[(b1<<2)&0x3f|(b2>>>6)&0x03];
output[outOffset++]=nibble2code[b2&077];
}
switch(remaining)
{
case 0:
break;
case 1:
b0=input[inOffset++];
output[outOffset++]=nibble2code[(b0>>>2)&0x3f];
output[outOffset++]=nibble2code[(b0<<4)&0x3f];
output[outOffset++]=pad;
output[outOffset++]=pad;
break;
case 2:
b0=input[inOffset++];
b1=input[inOffset++];
output[outOffset++]=nibble2code[(b0>>>2)&0x3f];
output[outOffset++]=nibble2code[(b0<<4)&0x3f|(b1>>>4)&0x0f];
output[outOffset++]=nibble2code[(b1<<2)&0x3f];
output[outOffset++]=pad;
break;
default:
throw new IllegalStateException("should not happen");
}
}
private static int encodeExplicit(final byte[] input, int inOffset, final int inLen,
final byte[] output, int outOffset, int loops)
{
for (byte b0, b1, b2; loops-->0;)
{
b0=input[inOffset++];
b1=input[inOffset++];
b2=input[inOffset++];
output[outOffset++]=nibble2code[(b0>>>2)&0x3f];
output[outOffset++]=nibble2code[(b0<<4)&0x3f|(b1>>>4)&0x0f];
output[outOffset++]=nibble2code[(b1<<2)&0x3f|(b2>>>6)&0x03];
output[outOffset++]=nibble2code[b2&077];
}
return inOffset;
}
/**
* Encodes the byte array into the {@link LinkedBuffer} and grows when full.
*/
public static LinkedBuffer encode(final byte[] input, int inOffset, int inLen,
final WriteSession session, final LinkedBuffer lb) throws IOException
{
int outputSize = ((inLen+2)/3)*4;
session.size += outputSize;
final int available = lb.buffer.length - lb.offset;
if(outputSize > available)
{
int chunks = available/4;
if(chunks == 0)
{
// available size is less than 4
if(outputSize > session.nextBufferSize)
{
final byte[] encoded = new byte[outputSize];
encode(input, inOffset, inLen, encoded, 0);
// return a fresh buffer.
return new LinkedBuffer(session.nextBufferSize,
new LinkedBuffer(encoded, 0, outputSize, lb));
}
final byte[] encoded = new byte[session.nextBufferSize];
encode(input, inOffset, inLen, encoded, 0);
// continue with the existing byte array of the previous buffer
return new LinkedBuffer(encoded, 0, outputSize, lb);
}
int inBefore = inOffset;
final byte[] buffer = lb.buffer;
int offset = lb.offset;
byte b0, b1, b2;
// process available
while(chunks-->0)
{
b0=input[inOffset++];
b1=input[inOffset++];
b2=input[inOffset++];
buffer[offset++]=nibble2code[(b0>>>2)&0x3f];
buffer[offset++]=nibble2code[(b0<<4)&0x3f|(b1>>>4)&0x0f];
buffer[offset++]=nibble2code[(b1<<2)&0x3f|(b2>>>6)&0x03];
buffer[offset++]=nibble2code[b2&077];
}
inLen -= (inOffset - inBefore);
outputSize -= (offset - lb.offset);
lb.offset = offset;
if(outputSize > session.nextBufferSize)
{
final byte[] encoded = new byte[outputSize];
encode(input, inOffset, inLen, encoded, 0);
// return a fresh buffer.
return new LinkedBuffer(session.nextBufferSize,
new LinkedBuffer(encoded, 0, outputSize, lb));
}
final byte[] encoded = new byte[session.nextBufferSize];
encode(input, inOffset, inLen, encoded, 0);
// continue with the existing byte array of the previous buffer
return new LinkedBuffer(encoded, 0, outputSize, lb);
}
encode(input, inOffset, inLen, lb.buffer, lb.offset);
lb.offset += outputSize;
return lb;
}
/**
* Encodes the byte array into the {@link LinkedBuffer} and flushes
* to the {@link OutputStream} when buffer is full.
*/
public static LinkedBuffer encode(final byte[] input, int inOffset, int inLen,
final WriteSession session, final OutputStream out,
final LinkedBuffer lb) throws IOException
{
int outputSize = ((inLen+2)/3)*4;
session.size += outputSize;
final int available = lb.buffer.length - lb.offset;
if(outputSize > available)
{
final int bufSize = lb.buffer.length - lb.start;
if(outputSize > bufSize)
{
// still larger than buffer size.
int chunks = available/4;
if(chunks == 0)
{
// available size is less than 4
// flush and reset
out.write(lb.buffer, lb.start, lb.offset-lb.start);
lb.offset = lb.start;
final int loops = bufSize/4, inProcessLen = loops*3, outProcessLen = loops*4;
do
{
inOffset = encodeExplicit(input, inOffset, 0, lb.buffer,
lb.offset, loops);
out.write(lb.buffer, lb.offset, outProcessLen);
inLen -= inProcessLen;
outputSize -= outProcessLen;
}
while(inLen > inProcessLen);
// write remaining
encode(input, inOffset, inLen, lb.buffer, lb.offset);
lb.offset += outputSize;
return lb;
}
int inBefore = inOffset;
byte[] buffer = lb.buffer;
int offset = lb.offset;
byte b0, b1, b2;
// process available
while(chunks-->0)
{
b0=input[inOffset++];
b1=input[inOffset++];
b2=input[inOffset++];
buffer[offset++]=nibble2code[(b0>>>2)&0x3f];
buffer[offset++]=nibble2code[(b0<<4)&0x3f|(b1>>>4)&0x0f];
buffer[offset++]=nibble2code[(b1<<2)&0x3f|(b2>>>6)&0x03];
buffer[offset++]=nibble2code[b2&077];
}
inLen -= (inOffset - inBefore);
outputSize -= (offset - lb.offset);
// flush available
out.write(buffer, lb.start, offset-lb.start);
// reset
lb.offset = lb.start;
if(outputSize > bufSize)
{
// still larger than buffer size.
final int loops = bufSize/4, inProcessLen = loops*3, outProcessLen = loops*4;
do
{
inOffset = encodeExplicit(input, inOffset, 0, lb.buffer,
lb.offset, loops);
out.write(lb.buffer, lb.offset, outProcessLen);
inLen -= inProcessLen;
outputSize -= outProcessLen;
}
while(inLen > inProcessLen);
// write remaining
encode(input, inOffset, inLen, lb.buffer, lb.offset);
lb.offset += outputSize;
return lb;
}
encode(input, inOffset, inLen, lb.buffer, lb.offset);
lb.offset += outputSize;
return lb;
}
// flush and reset
out.write(lb.buffer, lb.start, lb.offset-lb.start);
lb.offset = lb.start;
}
encode(input, inOffset, inLen, lb.buffer, lb.offset);
lb.offset += outputSize;
return lb;
}
/**
* Fast Base 64 decode as described in RFC 1421.
*/
public static byte[] decode(final byte[] b)
{
return decode(b, 0, b.length);
}
/* ------------------------------------------------------------ */
/**
* Fast Base 64 decode as described in RFC 1421.
*
Does not attempt to cope with extra whitespace
* as described in RFC 1521.
*
Avoids creating extra copies of the input/output.
*
Note this code has been flattened for performance.
* @param input byte array to decode.
* @param inOffset the offset.
* @param inLen the length.
* @return byte array containing the decoded form of the input.
* @throws IllegalArgumentException if the input is not a valid
* B64 encoding.
*/
public static byte[] decode(final byte[] input, int inOffset, final int inLen)
{
if(inLen == 0)
return ByteString.EMPTY_BYTE_ARRAY;
if (inLen%4!=0)
throw new IllegalArgumentException("Input block size is not 4");
int withoutPaddingLen = inLen, limit = inOffset + inLen;
while (input[--limit]==pad)
withoutPaddingLen--;
// Create result array of exact required size.
final int outLen=((withoutPaddingLen)*3)/4;
final byte[] output=new byte[outLen];
decode(input, inOffset, inLen, output, 0, outLen);
return output;
}
/**
* Returns the length of the decoded base64 input (written to the
* provided {@code output} byte array).
* The {@code output} byte array must have enough capacity or it will fail.
*/
public static int decodeTo(final byte[] output, int outOffset,
final byte[] input, int inOffset, final int inLen)
{
if(inLen == 0)
return 0;
if (inLen%4!=0)
throw new IllegalArgumentException("Input block size is not 4");
int withoutPaddingLen = inLen, limit = inOffset + inLen;
while (input[--limit]==pad)
withoutPaddingLen--;
// Create result array of exact required size.
final int outLen=((withoutPaddingLen)*3)/4;
//assert (output.length - outOffset) >= outLen;
decode(input, inOffset, inLen, output, outOffset, outLen);
return outLen;
}
private static void decode(final byte[] input, int inOffset, final int inLen,
final byte[] output, int outOffset, final int outLen)
{
int stop=(outLen/3)*3;
byte b0,b1,b2,b3;
try
{
while (outOffset>>4);
output[outOffset++]=(byte)(b1<<4|b2>>>2);
output[outOffset++]=(byte)(b2<<6|b3);
}
if (outLen!=outOffset)
{
switch (outLen%3)
{
case 0:
break;
case 1:
b0=code2nibble[input[inOffset++]];
b1=code2nibble[input[inOffset++]];
if (b0<0 || b1<0)
throw new IllegalArgumentException("Not B64 encoded");
output[outOffset++]=(byte)(b0<<2|b1>>>4);
break;
case 2:
b0=code2nibble[input[inOffset++]];
b1=code2nibble[input[inOffset++]];
b2=code2nibble[input[inOffset++]];
if (b0<0 || b1<0 || b2<0)
throw new IllegalArgumentException("Not B64 encoded");
output[outOffset++]=(byte)(b0<<2|b1>>>4);
output[outOffset++]=(byte)(b1<<4|b2>>>2);
break;
default:
throw new IllegalStateException("should not happen");
}
}
}
catch (IndexOutOfBoundsException e)
{
throw new IllegalArgumentException("char "+inOffset
+" was not B64 encoded");
}
}
/**
* Returns the base 64 decoded bytes.
* The provided {@code str} must already be base-64 encoded.
*/
public static byte[] decode(final String str)
{
return decode(str, 0, str.length());
}
/**
* Returns the base 64 decoded bytes.
* The provided {@code str} must already be base-64 encoded.
*/
public static byte[] decode(final String str, int inOffset, final int inLen)
{
if(inLen == 0)
return new byte[0];
if (inLen%4!=0)
throw new IllegalArgumentException("Input block size is not 4");
int withoutPaddingLen = inLen, limit = inOffset + inLen;
while (str.charAt(--limit)==pad)
withoutPaddingLen--;
// Create result array of exact required size.
final int outLen=((withoutPaddingLen)*3)/4;
final byte[] output=new byte[outLen];
decode(str, inOffset, inLen, output, 0, outLen);
return output;
}
/**
* Returns the length of the decoded base64 input (written to the
* provided {@code output} byte array).
* The {@code output} byte array must have enough capacity or it will fail.
*/
public static int decodeTo(final byte[] output, int outOffset,
final String str, int inOffset, final int inLen)
{
if(inLen == 0)
return 0;
if (inLen%4!=0)
throw new IllegalArgumentException("Input block size is not 4");
int withoutPaddingLen = inLen, limit = inOffset + inLen;
while (str.charAt(--limit)==pad)
withoutPaddingLen--;
// Create result array of exact required size.
final int outLen=((withoutPaddingLen)*3)/4;
//assert (output.length - outOffset) >= outLen;
decode(str, inOffset, inLen, output, outOffset, outLen);
return outLen;
}
private static void decode(final String str, int inOffset, final int inLen,
final byte[] output, int outOffset, final int outLen)
{
int stop=(outLen/3)*3;
byte b0,b1,b2,b3;
try
{
while (outOffset>>4);
output[outOffset++]=(byte)(b1<<4|b2>>>2);
output[outOffset++]=(byte)(b2<<6|b3);
}
if (outLen!=outOffset)
{
switch (outLen%3)
{
case 0:
break;
case 1:
b0=code2nibble[str.charAt(inOffset++)];
b1=code2nibble[str.charAt(inOffset++)];
if (b0<0 || b1<0)
throw new IllegalArgumentException("Not B64 encoded");
output[outOffset++]=(byte)(b0<<2|b1>>>4);
break;
case 2:
b0=code2nibble[str.charAt(inOffset++)];
b1=code2nibble[str.charAt(inOffset++)];
b2=code2nibble[str.charAt(inOffset++)];
if (b0<0 || b1<0 || b2<0)
throw new IllegalArgumentException("Not B64 encoded");
output[outOffset++]=(byte)(b0<<2|b1>>>4);
output[outOffset++]=(byte)(b1<<4|b2>>>2);
break;
default:
throw new IllegalStateException("should not happen");
}
}
}
catch (IndexOutOfBoundsException e)
{
throw new IllegalArgumentException("char "+inOffset
+" was not B64 encoded");
}
}
}