blob: 20e56467b0c10074de4fc6df41f8de88804ca5c9 [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.
*/
/**
* @author Alexander Y. Kleymenov
* @version $Revision$
*/
package org.apache.harmony.security.provider.cert;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charsets;
import java.security.cert.CRL;
import java.security.cert.CRLException;
import java.security.cert.CertPath;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactorySpi;
import java.security.cert.X509CRL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.harmony.luni.util.Base64;
import org.apache.harmony.security.asn1.ASN1Constants;
import org.apache.harmony.security.asn1.BerInputStream;
import org.apache.harmony.security.pkcs7.ContentInfo;
import org.apache.harmony.security.pkcs7.SignedData;
import org.apache.harmony.security.x509.CertificateList;
/**
* X509 Certificate Factory Service Provider Interface Implementation.
* It supports CRLs and Certificates in (PEM) ASN.1 DER encoded form,
* and Certification Paths in PkiPath and PKCS7 formats.
* For Certificates and CRLs factory maintains the caching
* mechanisms allowing to speed up repeated Certificate/CRL
* generation.
* @see Cache
*/
public class X509CertFactoryImpl extends CertificateFactorySpi {
// number of leading/trailing bytes used for cert hash computation
private static final int CERT_CACHE_SEED_LENGTH = 28;
// certificate cache
private static final Cache CERT_CACHE = new Cache(CERT_CACHE_SEED_LENGTH);
// number of leading/trailing bytes used for crl hash computation
private static final int CRL_CACHE_SEED_LENGTH = 24;
// crl cache
private static final Cache CRL_CACHE = new Cache(CRL_CACHE_SEED_LENGTH);
/**
* Default constructor.
* Creates the instance of Certificate Factory SPI ready for use.
*/
public X509CertFactoryImpl() { }
/**
* Generates the X.509 certificate from the data in the stream.
* The data in the stream can be either in ASN.1 DER encoded X.509
* certificate, or PEM (Base64 encoding bounded by
* <code>"-----BEGIN CERTIFICATE-----"</code> at the beginning and
* <code>"-----END CERTIFICATE-----"</code> at the end) representation
* of the former encoded form.
*
* Before the generation the encoded form is looked up in
* the cache. If the cache contains the certificate with requested encoded
* form it is returned from it, otherwise it is generated by ASN.1
* decoder.
*
* @see java.security.cert.CertificateFactorySpi#engineGenerateCertificate(InputStream)
* method documentation for more info
*/
public Certificate engineGenerateCertificate(InputStream inStream)
throws CertificateException {
if (inStream == null) {
throw new CertificateException("inStream == null");
}
try {
if (!inStream.markSupported()) {
// create the mark supporting wrapper
inStream = new RestoringInputStream(inStream);
}
// mark is needed to recognize the format of the provided encoding
// (ASN.1 or PEM)
inStream.mark(1);
// check whether the provided certificate is in PEM encoded form
if (inStream.read() == '-') {
// decode PEM, retrieve CRL
return getCertificate(decodePEM(inStream, CERT_BOUND_SUFFIX));
} else {
inStream.reset();
// retrieve CRL
return getCertificate(inStream);
}
} catch (IOException e) {
throw new CertificateException(e);
}
}
/**
* Generates the collection of the certificates on the base of provided
* via input stream encodings.
* @see java.security.cert.CertificateFactorySpi#engineGenerateCertificates(InputStream)
* method documentation for more info
*/
public Collection<? extends Certificate>
engineGenerateCertificates(InputStream inStream)
throws CertificateException {
if (inStream == null) {
throw new CertificateException("inStream == null");
}
ArrayList result = new ArrayList();
try {
if (!inStream.markSupported()) {
// create the mark supporting wrapper
inStream = new RestoringInputStream(inStream);
}
// if it is PEM encoded form this array will contain the encoding
// so ((it is PEM) <-> (encoding != null))
byte[] encoding = null;
// The following by SEQUENCE ASN.1 tag, used for
// recognizing the data format
// (is it PKCS7 ContentInfo structure, X.509 Certificate, or
// unsupported encoding)
int second_asn1_tag = -1;
inStream.mark(1);
int ch;
while ((ch = inStream.read()) != -1) {
// check if it is PEM encoded form
if (ch == '-') { // beginning of PEM encoding ('-' char)
// decode PEM chunk and store its content (ASN.1 encoding)
encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
} else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
encoding = null;
inStream.reset();
// prepare for data format determination
inStream.mark(CERT_CACHE_SEED_LENGTH);
} else { // unsupported data
if (result.size() == 0) {
throw new CertificateException("Unsupported encoding");
} else {
// it can be trailing user data,
// so keep it in the stream
inStream.reset();
return result;
}
}
// Check the data format
BerInputStream in = (encoding == null)
? new BerInputStream(inStream)
: new BerInputStream(encoding);
// read the next ASN.1 tag
second_asn1_tag = in.next(); // inStream position changed
if (encoding == null) {
// keep whole structure in the stream
inStream.reset();
}
// check if it is a TBSCertificate structure
if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
if (result.size() == 0) {
// there were not read X.509 Certificates, so
// break the cycle and check
// whether it is PKCS7 structure
break;
} else {
// it can be trailing user data,
// so return what we already read
return result;
}
} else {
if (encoding == null) {
result.add(getCertificate(inStream));
} else {
result.add(getCertificate(encoding));
}
}
// mark for the next iteration
inStream.mark(1);
}
if (result.size() != 0) {
// some Certificates have been read
return result;
} else if (ch == -1) {
throw new CertificateException("There is no data in the stream");
}
// else: check if it is PKCS7
if (second_asn1_tag == ASN1Constants.TAG_OID) {
// it is PKCS7 ContentInfo structure, so decode it
ContentInfo info = (ContentInfo)
((encoding != null)
? ContentInfo.ASN1.decode(encoding)
: ContentInfo.ASN1.decode(inStream));
// retrieve SignedData
SignedData data = info.getSignedData();
if (data == null) {
throw new CertificateException("Invalid PKCS7 data provided");
}
List certs = data.getCertificates();
if (certs != null) {
for (int i = 0; i < certs.size(); i++) {
result.add(new X509CertImpl(
(org.apache.harmony.security.x509.Certificate)
certs.get(i)));
}
}
return result;
}
// else: Unknown data format
throw new CertificateException("Unsupported encoding");
} catch (IOException e) {
throw new CertificateException(e);
}
}
/**
* @see java.security.cert.CertificateFactorySpi#engineGenerateCRL(InputStream)
* method documentation for more info
*/
public CRL engineGenerateCRL(InputStream inStream)
throws CRLException {
if (inStream == null) {
throw new CRLException("inStream == null");
}
try {
if (!inStream.markSupported()) {
// Create the mark supporting wrapper
// Mark is needed to recognize the format
// of provided encoding form (ASN.1 or PEM)
inStream = new RestoringInputStream(inStream);
}
inStream.mark(1);
// check whether the provided crl is in PEM encoded form
if (inStream.read() == '-') {
// decode PEM, retrieve CRL
return getCRL(decodePEM(inStream, FREE_BOUND_SUFFIX));
} else {
inStream.reset();
// retrieve CRL
return getCRL(inStream);
}
} catch (IOException e) {
throw new CRLException(e);
}
}
/**
* @see java.security.cert.CertificateFactorySpi#engineGenerateCRLs(InputStream)
* method documentation for more info
*/
public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream)
throws CRLException {
if (inStream == null) {
throw new CRLException("inStream == null");
}
ArrayList result = new ArrayList();
try {
if (!inStream.markSupported()) {
inStream = new RestoringInputStream(inStream);
}
// if it is PEM encoded form this array will contain the encoding
// so ((it is PEM) <-> (encoding != null))
byte[] encoding = null;
// The following by SEQUENCE ASN.1 tag, used for
// recognizing the data format
// (is it PKCS7 ContentInfo structure, X.509 CRL, or
// unsupported encoding)
int second_asn1_tag = -1;
inStream.mark(1);
int ch;
while ((ch = inStream.read()) != -1) {
// check if it is PEM encoded form
if (ch == '-') { // beginning of PEM encoding ('-' char)
// decode PEM chunk and store its content (ASN.1 encoding)
encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
} else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
encoding = null;
inStream.reset();
// prepare for data format determination
inStream.mark(CRL_CACHE_SEED_LENGTH);
} else { // unsupported data
if (result.size() == 0) {
throw new CRLException("Unsupported encoding");
} else {
// it can be trailing user data,
// so keep it in the stream
inStream.reset();
return result;
}
}
// Check the data format
BerInputStream in = (encoding == null)
? new BerInputStream(inStream)
: new BerInputStream(encoding);
// read the next ASN.1 tag
second_asn1_tag = in.next();
if (encoding == null) {
// keep whole structure in the stream
inStream.reset();
}
// check if it is a TBSCertList structure
if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
if (result.size() == 0) {
// there were not read X.509 CRLs, so
// break the cycle and check
// whether it is PKCS7 structure
break;
} else {
// it can be trailing user data,
// so return what we already read
return result;
}
} else {
if (encoding == null) {
result.add(getCRL(inStream));
} else {
result.add(getCRL(encoding));
}
}
inStream.mark(1);
}
if (result.size() != 0) {
// the stream was read out
return result;
} else if (ch == -1) {
throw new CRLException("There is no data in the stream");
}
// else: check if it is PKCS7
if (second_asn1_tag == ASN1Constants.TAG_OID) {
// it is PKCS7 ContentInfo structure, so decode it
ContentInfo info = (ContentInfo)
((encoding != null)
? ContentInfo.ASN1.decode(encoding)
: ContentInfo.ASN1.decode(inStream));
// retrieve SignedData
SignedData data = info.getSignedData();
if (data == null) {
throw new CRLException("Invalid PKCS7 data provided");
}
List crls = data.getCRLs();
if (crls != null) {
for (int i = 0; i < crls.size(); i++) {
result.add(new X509CRLImpl(
(CertificateList) crls.get(i)));
}
}
return result;
}
// else: Unknown data format
throw new CRLException("Unsupported encoding");
} catch (IOException e) {
throw new CRLException(e);
}
}
/**
* @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream)
* method documentation for more info
*/
public CertPath engineGenerateCertPath(InputStream inStream)
throws CertificateException {
if (inStream == null) {
throw new CertificateException("inStream == null");
}
return engineGenerateCertPath(inStream, "PkiPath");
}
/**
* @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream,String)
* method documentation for more info
*/
public CertPath engineGenerateCertPath(
InputStream inStream, String encoding) throws CertificateException {
if (inStream == null) {
throw new CertificateException("inStream == null");
}
if (!inStream.markSupported()) {
inStream = new RestoringInputStream(inStream);
}
try {
inStream.mark(1);
int ch;
// check if it is PEM encoded form
if ((ch = inStream.read()) == '-') {
// decode PEM chunk into ASN.1 form and decode CertPath object
return X509CertPathImpl.getInstance(
decodePEM(inStream, FREE_BOUND_SUFFIX), encoding);
} else if (ch == 0x30) { // ASN.1 Sequence
inStream.reset();
// decode ASN.1 form
return X509CertPathImpl.getInstance(inStream, encoding);
} else {
throw new CertificateException("Unsupported encoding");
}
} catch (IOException e) {
throw new CertificateException(e);
}
}
/**
* @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(List)
* method documentation for more info
*/
public CertPath engineGenerateCertPath(List certificates)
throws CertificateException {
return new X509CertPathImpl(certificates);
}
/**
* @see java.security.cert.CertificateFactorySpi#engineGetCertPathEncodings()
* method documentation for more info
*/
public Iterator<String> engineGetCertPathEncodings() {
return X509CertPathImpl.encodings.iterator();
}
// ---------------------------------------------------------------------
// ------------------------ Staff methods ------------------------------
// ---------------------------------------------------------------------
private static final byte[] PEM_BEGIN = "-----BEGIN".getBytes(Charsets.UTF_8);
private static final byte[] PEM_END = "-----END".getBytes(Charsets.UTF_8);
/**
* Code describing free format for PEM boundary suffix:
* "^-----BEGIN.*\n" at the beginning, and<br>
* "\n-----END.*(EOF|\n)$" at the end.
*/
private static final byte[] FREE_BOUND_SUFFIX = null;
/**
* Code describing PEM boundary suffix for X.509 certificate:
* "^-----BEGIN CERTIFICATE-----\n" at the beginning, and<br>
* "\n-----END CERTIFICATE-----" at the end.
*/
private static final byte[] CERT_BOUND_SUFFIX = " CERTIFICATE-----".getBytes(Charsets.UTF_8);
/**
* Method retrieves the PEM encoded data from the stream
* and returns its decoded representation.
* Method checks correctness of PEM boundaries. It supposes that
* the first '-' of the opening boundary has already been read from
* the stream. So first of all it checks that the leading bytes
* are equal to "-----BEGIN" boundary prefix. Than if boundary_suffix
* is not null, it checks that next bytes equal to boundary_suffix
* + new line char[s] ([CR]LF).
* If boundary_suffix parameter is null, method supposes free suffix
* format and skips any bytes until the new line.<br>
* After the opening boundary has been read and checked, the method
* read Base64 encoded data until closing PEM boundary is not reached.<br>
* Than it checks closing boundary - it should start with new line +
* "-----END" + boundary_suffix. If boundary_suffix is null,
* any characters are skipped until the new line.<br>
* After this any trailing new line characters are skipped from the stream,
* Base64 encoding is decoded and returned.
* @param inStream the stream containing the PEM encoding.
* @param boundary_suffix the suffix of expected PEM multipart
* boundary delimiter.<br>
* If it is null, that any character sequences are accepted.
* @throws IOException If PEM boundary delimiter does not comply
* with expected or some I/O or decoding problems occur.
*/
private byte[] decodePEM(InputStream inStream, byte[] boundary_suffix)
throws IOException {
int ch; // the char to be read
// check and skip opening boundary delimiter
// (first '-' is supposed as already read)
for (int i = 1; i < PEM_BEGIN.length; ++i) {
if (PEM_BEGIN[i] != (ch = inStream.read())) {
throw new IOException(
"Incorrect PEM encoding: '-----BEGIN"
+ ((boundary_suffix == null)
? "" : new String(boundary_suffix))
+ "' is expected as opening delimiter boundary.");
}
}
if (boundary_suffix == null) {
// read (skip) the trailing characters of
// the beginning PEM boundary delimiter
while ((ch = inStream.read()) != '\n') {
if (ch == -1) {
throw new IOException("Incorrect PEM encoding: EOF before content");
}
}
} else {
for (int i=0; i<boundary_suffix.length; i++) {
if (boundary_suffix[i] != inStream.read()) {
throw new IOException("Incorrect PEM encoding: '-----BEGIN" +
new String(boundary_suffix) + "' is expected as opening delimiter boundary.");
}
}
// read new line characters
if ((ch = inStream.read()) == '\r') {
// CR has been read, now read LF character
ch = inStream.read();
}
if (ch != '\n') {
throw new IOException("Incorrect PEM encoding: newline expected after " +
"opening delimiter boundary");
}
}
int size = 1024; // the size of the buffer containing Base64 data
byte[] buff = new byte[size];
int index = 0;
// read bytes while ending boundary delimiter is not reached
while ((ch = inStream.read()) != '-') {
if (ch == -1) {
throw new IOException("Incorrect Base64 encoding: EOF without closing delimiter");
}
buff[index++] = (byte) ch;
if (index == size) {
// enlarge the buffer
byte[] newbuff = new byte[size+1024];
System.arraycopy(buff, 0, newbuff, 0, size);
buff = newbuff;
size += 1024;
}
}
if (buff[index-1] != '\n') {
throw new IOException("Incorrect Base64 encoding: newline expected before " +
"closing boundary delimiter");
}
// check and skip closing boundary delimiter prefix
// (first '-' was read)
for (int i = 1; i < PEM_END.length; ++i) {
if (PEM_END[i] != inStream.read()) {
throw badEnd(boundary_suffix);
}
}
if (boundary_suffix == null) {
// read (skip) the trailing characters of
// the closing PEM boundary delimiter
while (((ch = inStream.read()) != -1) && (ch != '\n') && (ch != '\r')) {
}
} else {
for (int i=0; i<boundary_suffix.length; i++) {
if (boundary_suffix[i] != inStream.read()) {
throw badEnd(boundary_suffix);
}
}
}
// skip trailing line breaks
inStream.mark(1);
while (((ch = inStream.read()) != -1) && (ch == '\n' || ch == '\r')) {
inStream.mark(1);
}
inStream.reset();
buff = Base64.decode(buff, index);
if (buff == null) {
throw new IOException("Incorrect Base64 encoding");
}
return buff;
}
private IOException badEnd(byte[] boundary_suffix) throws IOException {
String s = (boundary_suffix == null) ? "" : new String(boundary_suffix);
throw new IOException("Incorrect PEM encoding: '-----END" + s + "' is expected as closing delimiter boundary.");
}
/**
* Reads the data of specified length from source
* and returns it as an array.
* @return the byte array contained read data or
* null if the stream contains not enough data
* @throws IOException if some I/O error has been occurred.
*/
private static byte[] readBytes(InputStream source, int length)
throws IOException {
byte[] result = new byte[length];
for (int i=0; i<length; i++) {
int bytik = source.read();
if (bytik == -1) {
return null;
}
result[i] = (byte) bytik;
}
return result;
}
/**
* Returns the Certificate object corresponding to the provided encoding.
* Resulting object is retrieved from the cache
* if it contains such correspondence
* and is constructed on the base of encoding
* and stored in the cache otherwise.
* @throws IOException if some decoding errors occur
* (in the case of cache miss).
*/
private static Certificate getCertificate(byte[] encoding)
throws CertificateException, IOException {
if (encoding.length < CERT_CACHE_SEED_LENGTH) {
throw new CertificateException("encoding.length < CERT_CACHE_SEED_LENGTH");
}
synchronized (CERT_CACHE) {
long hash = CERT_CACHE.getHash(encoding);
if (CERT_CACHE.contains(hash)) {
Certificate res =
(Certificate) CERT_CACHE.get(hash, encoding);
if (res != null) {
return res;
}
}
Certificate res = new X509CertImpl(encoding);
CERT_CACHE.put(hash, encoding, res);
return res;
}
}
/**
* Returns the Certificate object corresponding to the encoding provided
* by the stream.
* Resulting object is retrieved from the cache
* if it contains such correspondence
* and is constructed on the base of encoding
* and stored in the cache otherwise.
* @throws IOException if some decoding errors occur
* (in the case of cache miss).
*/
private static Certificate getCertificate(InputStream inStream)
throws CertificateException, IOException {
synchronized (CERT_CACHE) {
inStream.mark(CERT_CACHE_SEED_LENGTH);
// read the prefix of the encoding
byte[] buff = readBytes(inStream, CERT_CACHE_SEED_LENGTH);
inStream.reset();
if (buff == null) {
throw new CertificateException("InputStream doesn't contain enough data");
}
long hash = CERT_CACHE.getHash(buff);
if (CERT_CACHE.contains(hash)) {
byte[] encoding = new byte[BerInputStream.getLength(buff)];
if (encoding.length < CERT_CACHE_SEED_LENGTH) {
throw new CertificateException("Bad Certificate encoding");
}
inStream.read(encoding);
Certificate res = (Certificate) CERT_CACHE.get(hash, encoding);
if (res != null) {
return res;
}
res = new X509CertImpl(encoding);
CERT_CACHE.put(hash, encoding, res);
return res;
} else {
inStream.reset();
Certificate res = new X509CertImpl(inStream);
CERT_CACHE.put(hash, res.getEncoded(), res);
return res;
}
}
}
/**
* Returns the CRL object corresponding to the provided encoding.
* Resulting object is retrieved from the cache
* if it contains such correspondence
* and is constructed on the base of encoding
* and stored in the cache otherwise.
* @throws IOException if some decoding errors occur
* (in the case of cache miss).
*/
private static CRL getCRL(byte[] encoding)
throws CRLException, IOException {
if (encoding.length < CRL_CACHE_SEED_LENGTH) {
throw new CRLException("encoding.length < CRL_CACHE_SEED_LENGTH");
}
synchronized (CRL_CACHE) {
long hash = CRL_CACHE.getHash(encoding);
if (CRL_CACHE.contains(hash)) {
X509CRL res = (X509CRL) CRL_CACHE.get(hash, encoding);
if (res != null) {
return res;
}
}
X509CRL res = new X509CRLImpl(encoding);
CRL_CACHE.put(hash, encoding, res);
return res;
}
}
/**
* Returns the CRL object corresponding to the encoding provided
* by the stream.
* Resulting object is retrieved from the cache
* if it contains such correspondence
* and is constructed on the base of encoding
* and stored in the cache otherwise.
* @throws IOException if some decoding errors occur
* (in the case of cache miss).
*/
private static CRL getCRL(InputStream inStream)
throws CRLException, IOException {
synchronized (CRL_CACHE) {
inStream.mark(CRL_CACHE_SEED_LENGTH);
byte[] buff = readBytes(inStream, CRL_CACHE_SEED_LENGTH);
// read the prefix of the encoding
inStream.reset();
if (buff == null) {
throw new CRLException("InputStream doesn't contain enough data");
}
long hash = CRL_CACHE.getHash(buff);
if (CRL_CACHE.contains(hash)) {
byte[] encoding = new byte[BerInputStream.getLength(buff)];
if (encoding.length < CRL_CACHE_SEED_LENGTH) {
throw new CRLException("Bad CRL encoding");
}
inStream.read(encoding);
CRL res = (CRL) CRL_CACHE.get(hash, encoding);
if (res != null) {
return res;
}
res = new X509CRLImpl(encoding);
CRL_CACHE.put(hash, encoding, res);
return res;
} else {
X509CRL res = new X509CRLImpl(inStream);
CRL_CACHE.put(hash, res.getEncoded(), res);
return res;
}
}
}
/*
* This class extends any existing input stream with
* mark functionality. It acts as a wrapper over the
* stream and supports reset to the
* marked state with readlimit no more than BUFF_SIZE.
*/
private static class RestoringInputStream extends InputStream {
// wrapped input stream
private final InputStream inStream;
// specifies how much of the read data is buffered
// after the mark has been set up
private static final int BUFF_SIZE = 32;
// buffer to keep the bytes read after the mark has been set up
private final int[] buff = new int[BUFF_SIZE*2];
// position of the next byte to read,
// the value of -1 indicates that the buffer is not used
// (mark was not set up or was invalidated, or reset to the marked
// position has been done and all the buffered data was read out)
private int pos = -1;
// position of the last buffered byte
private int bar = 0;
// position in the buffer where the mark becomes invalidated
private int end = 0;
/**
* Creates the mark supporting wrapper over the stream.
*/
public RestoringInputStream(InputStream inStream) {
this.inStream = inStream;
}
@Override
public int available() throws IOException {
return (bar - pos) + inStream.available();
}
@Override
public void close() throws IOException {
inStream.close();
}
@Override
public void mark(int readlimit) {
if (pos < 0) {
pos = 0;
bar = 0;
end = BUFF_SIZE - 1;
} else {
end = (pos + BUFF_SIZE - 1) % BUFF_SIZE;
}
}
@Override
public boolean markSupported() {
return true;
}
/**
* Reads the byte from the stream. If mark has been set up
* and was not invalidated byte is read from the underlying
* stream and saved into the buffer. If the current read position
* has been reset to the marked position and there are remaining
* bytes in the buffer, the byte is taken from it. In the other cases
* (if mark has been invalidated, or there are no buffered bytes)
* the byte is taken directly from the underlying stream and it is
* returned without saving to the buffer.
*
* @see java.io.InputStream#read()
* method documentation for more info
*/
public int read() throws IOException {
// if buffer is currently used
if (pos >= 0) {
// current position in the buffer
int cur = pos % BUFF_SIZE;
// check whether the buffer contains the data to be read
if (cur < bar) {
// return the data from the buffer
pos++;
return buff[cur];
}
// check whether buffer has free space
if (cur != end) {
// it has, so read the data from the wrapped stream
// and place it in the buffer
buff[cur] = inStream.read();
bar = cur+1;
pos++;
return buff[cur];
} else {
// buffer if full and can not operate
// any more, so invalidate the mark position
// and turn off the using of buffer
pos = -1;
}
}
// buffer is not used, so return the data from the wrapped stream
return inStream.read();
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int read_b;
int i;
for (i=0; i<len; i++) {
if ((read_b = read()) == -1) {
return (i == 0) ? -1 : i;
}
b[off+i] = (byte) read_b;
}
return i;
}
@Override
public void reset() throws IOException {
if (pos >= 0) {
pos = (end + 1) % BUFF_SIZE;
} else {
throw new IOException("Could not reset the stream: " +
"position became invalid or stream has not been marked");
}
}
@Override
public long skip(long n) throws IOException {
if (pos >= 0) {
long i = 0;
int av = available();
if (av < n) {
n = av;
}
while ((i < n) && (read() != -1)) {
i++;
}
return i;
} else {
return inStream.skip(n);
}
}
}
}