com.helger.commons.base64.Base64OutputStream Maven / Gradle / Ivy
Show all versions of ph-commons Show documentation
/*
* Copyright (C) 2014-2024 Philip Helger (www.helger.com)
* philip[at]helger[dot]com
*
* 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.helger.commons.base64;
import java.io.IOException;
import java.io.OutputStream;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.collection.ArrayHelper;
import com.helger.commons.io.stream.WrappedOutputStream;
/**
* A {@link Base64OutputStream} will write data to another
* OutputStream
, given in the constructor, and encode/decode
* to/from Base64 notation on the fly.
*
* @see Base64
* @since 1.3
*/
@NotThreadSafe
public class Base64OutputStream extends WrappedOutputStream
{
private final boolean m_bEncode;
private int m_nPosition;
private byte [] m_aBuffer;
private final int m_nBufferLength;
private int m_nLineLength;
private final boolean m_bBreakLines;
// Scratch used in a few places
private final byte [] m_aB4;
private boolean m_bSuspendEncoding;
// Record for later
private final int m_nOptions;
// Local copies to avoid extra method calls
private final byte [] m_aDecodabet;
private byte [] m_aNewLineBytes = new byte [] { Base64.NEW_LINE };
/**
* Constructs a {@link Base64OutputStream} in ENCODE mode.
*
* @param aOS
* the OutputStream
to which data will be written.
* @since 1.3
*/
public Base64OutputStream (@Nonnull final OutputStream aOS)
{
this (aOS, Base64.ENCODE);
}
/**
* Constructs a {@link Base64OutputStream} in either ENCODE or DECODE mode.
*
* Valid options:
*
*
* ENCODE or DECODE: Encode or Decode as data is read.
* DO_BREAK_LINES: don't break lines at 76 characters
* (only meaningful when encoding)
*
*
* Example: new Base64.OutputStream( out, Base64.ENCODE )
*
* @param aOS
* the OutputStream
to which data will be written.
* @param nOptions
* Specified options.
* @see Base64#ENCODE
* @see Base64#DECODE
* @see Base64#DO_BREAK_LINES
* @since 1.3
*/
public Base64OutputStream (@Nonnull final OutputStream aOS, final int nOptions)
{
super (aOS);
m_bBreakLines = (nOptions & Base64.DO_BREAK_LINES) != 0;
m_bEncode = (nOptions & Base64.ENCODE) != 0;
m_nBufferLength = m_bEncode ? 3 : 4;
m_aBuffer = new byte [m_nBufferLength];
m_nPosition = 0;
m_nLineLength = 0;
m_bSuspendEncoding = false;
m_aB4 = new byte [4];
m_nOptions = nOptions;
m_aDecodabet = Base64._getDecodabet (nOptions);
}
/**
* Set the newline bytes to be used, so that "\r\n" can be used instead of the
* default "\n"
*
* @param aNewLineBytes
* The newline bytes to be used. May neither be null
nor
* empty.
* @since 9.3.4
*/
public void setNewLineBytes (@Nonnull @Nonempty final byte [] aNewLineBytes)
{
ValueEnforcer.notEmpty (aNewLineBytes, "NewLineBytes");
m_aNewLineBytes = ArrayHelper.getCopy (aNewLineBytes);
}
/**
* 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 (final int theByte) throws IOException
{
// Encoding suspended?
if (m_bSuspendEncoding)
{
out.write (theByte);
return;
}
// Encode?
if (m_bEncode)
{
m_aBuffer[m_nPosition++] = (byte) theByte;
if (m_nPosition >= m_nBufferLength)
{
// Enough to encode.
out.write (Base64._encode3to4 (m_aB4, m_aBuffer, m_nBufferLength, m_nOptions));
m_nLineLength += 4;
if (m_bBreakLines && m_nLineLength >= Base64.MAX_LINE_LENGTH)
{
out.write (m_aNewLineBytes);
m_nLineLength = 0;
}
m_nPosition = 0;
}
}
else
{
// Else, Decoding
// Meaningful Base64 character?
if (m_aDecodabet[theByte & 0x7f] > Base64.WHITE_SPACE_ENC)
{
m_aBuffer[m_nPosition++] = (byte) theByte;
if (m_nPosition >= m_nBufferLength)
{ // Enough to output.
final int len = Base64._decode4to3 (m_aBuffer, 0, m_aB4, 0, m_nOptions);
out.write (m_aB4, 0, len);
m_nPosition = 0;
}
}
else
if (m_aDecodabet[theByte & 0x7f] != Base64.WHITE_SPACE_ENC)
{
throw new IOException ("Invalid character in Base64 data.");
}
}
}
/**
* Calls {@link #write(int)} repeatedly until len bytes are
* written.
*
* @param aBytes
* array from which to read bytes
* @param nOfs
* offset for array
* @param nLen
* max number of bytes to read into array
* @since 1.3
*/
@Override
public void write (@Nonnull final byte [] aBytes, @Nonnegative final int nOfs, @Nonnegative final int nLen) throws IOException
{
// Encoding suspended?
if (m_bSuspendEncoding)
{
out.write (aBytes, nOfs, nLen);
return;
}
for (int i = 0; i < nLen; i++)
write (aBytes[nOfs + i]);
}
/**
* Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without
* closing the stream.
*
* @throws IOException
* if there's an error.
*/
public void flushBase64 () throws IOException
{
if (m_nPosition > 0)
{
if (m_bEncode)
{
out.write (Base64._encode3to4 (m_aB4, m_aBuffer, m_nPosition, m_nOptions));
m_nPosition = 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.
if (out != null)
super.close ();
m_aBuffer = null;
out = null;
}
/**
* Suspends encoding of the stream. May be helpful if you need to embed a
* piece of base64-encoded data in a stream.
*
* @throws IOException
* if there's an error flushing
* @since 1.5.1
*/
public void suspendEncoding () throws IOException
{
flushBase64 ();
m_bSuspendEncoding = true;
}
/**
* Resumes encoding of the stream. May be helpful if you need to embed a piece
* of base64-encoded data in a stream.
*
* @since 1.5.1
*/
public void resumeEncoding ()
{
m_bSuspendEncoding = false;
}
}