blob: 134553fa27df50630617aec2edffdec45f36269b [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.
*/
package org.apache.cxf.attachment;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.cxf.common.util.Base64Exception;
import org.apache.cxf.common.util.Base64Utility;
/**
* An implementation of a FilterInputStream that decodes the
* stream data in BASE64 encoding format. This version does the
* decoding "on the fly" rather than decoding a single block of
* data. Since this version is intended for use by the MimeUtilty class,
* it also handles line breaks in the encoded data.
*/
public class Base64DecoderStream extends FilterInputStream {
static final String MAIL_BASE64_IGNOREERRORS = "mail.mime.base64.ignoreerrors";
// number of decodeable units we'll try to process at one time. We'll attempt to read that much
// data from the input stream and decode in blocks.
static final int BUFFERED_UNITS = 2000;
// can be overridden by a system property.
protected boolean ignoreErrors;
// buffer for reading in chars for decoding (which can support larger bulk reads)
protected char[] encodedChars = new char[BUFFERED_UNITS * 4];
// a buffer for one decoding unit's worth of data (3 bytes).
protected byte[] decodedChars;
// count of characters in the buffer
protected int decodedCount;
// index of the next decoded character
protected int decodedIndex;
public Base64DecoderStream(InputStream in) {
super(in);
}
/**
* Test for the existance of decoded characters in our buffer
* of decoded data.
*
* @return True if we currently have buffered characters.
*/
private boolean dataAvailable() {
return decodedCount != 0;
}
/**
* Decode a requested number of bytes of data into a buffer.
*
* @return true if we were able to obtain more data, false otherwise.
*/
private boolean decodeStreamData() throws IOException {
decodedIndex = 0;
// fill up a data buffer with input data
int readCharacters = fillEncodedBuffer();
if (readCharacters > 0) {
try {
decodedChars = Base64Utility.decodeChunk(encodedChars, 0, readCharacters);
} catch (Base64Exception e) {
throw new IOException(e);
}
decodedCount = decodedChars.length;
return true;
}
return false;
}
/**
* Retrieve a single byte from the decoded characters buffer.
*
* @return The decoded character or -1 if there was an EOF condition.
*/
private int getByte() throws IOException {
if (!dataAvailable() && !decodeStreamData()) {
return -1;
}
decodedCount--;
// we need to ensure this doesn't get sign extended
return decodedChars[decodedIndex++] & 0xff;
}
private int getBytes(byte[] data, int offset, int length) throws IOException {
int readCharacters = 0;
while (length > 0) {
// need data? Try to get some
if (!dataAvailable() && !decodeStreamData()) {
// if we can't get this, return a count of how much we did get (which may be -1).
return readCharacters > 0 ? readCharacters : -1;
}
// now copy some of the data from the decoded buffer to the target buffer
int copyCount = Math.min(decodedCount, length);
System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount);
decodedIndex += copyCount;
decodedCount -= copyCount;
offset += copyCount;
length -= copyCount;
readCharacters += copyCount;
}
return readCharacters;
}
/**
* Fill our buffer of input characters for decoding from the
* stream. This will attempt read a full buffer, but will
* terminate on an EOF or read error. This will filter out
* non-Base64 encoding chars and will only return a valid
* multiple of 4 number of bytes.
*
* @return The count of characters read.
*/
private int fillEncodedBuffer() throws IOException {
int readCharacters = 0;
while (true) {
// get the next character from the stream
int ch = in.read();
// did we hit an EOF condition?
if (ch == -1) {
// now check to see if this is normal, or potentially an error
// if we didn't get characters as a multiple of 4, we may need to complain about this.
if ((readCharacters % 4) != 0) {
throw new IOException("Base64 encoding error, data truncated: " + readCharacters + " "
+ new String(encodedChars, 0, readCharacters));
}
// return the count.
return readCharacters;
} else if (Base64Utility.isValidBase64(ch)) {
// if this character is valid in a Base64 stream, copy it to the buffer.
encodedChars[readCharacters++] = (char)ch;
// if we've filled up the buffer, time to quit.
if (readCharacters >= encodedChars.length) {
return readCharacters;
}
}
// we're filtering out whitespace and CRLF characters, so just ignore these
}
}
// in order to function as a filter, these streams need to override the different
// read() signature.
@Override
public int read() throws IOException {
return getByte();
}
@Override
public int read(byte [] buffer, int offset, int length) throws IOException {
return getBytes(buffer, offset, length);
}
@Override
public boolean markSupported() {
return false;
}
@Override
public int available() throws IOException {
return ((in.available() / 4) * 3) + decodedCount;
}
}