/** * Copyright (c) 2009 - 2011 AppWork UG(haftungsbeschränkt) <e-mail@appwork.org> * * This file is part of org.appwork.utils.net * * This software is licensed under the Artistic License 2.0, * see the LICENSE file or http://www.opensource.org/licenses/artistic-license-2.0.php * for details */ package org.appwork.utils.net; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; /** * @author daniel * * Base64InputStream, based on RFC R2045, section 6.8 */ public class Base64InputStream extends FilterInputStream { private static final char[] BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); private static final byte[] BASE64MAP; static { /* build Base64 mapping */ BASE64MAP = new byte[256]; for (int i = 0; i < 255; i++) { Base64InputStream.BASE64MAP[i] = -1; } for (int i = 0; i < Base64InputStream.BASE64.length; i++) { Base64InputStream.BASE64MAP[Base64InputStream.BASE64[i]] = (byte) i; } } private final byte[] base64Encoded = new byte[4]; private final byte[] base64Decoded = new byte[3]; private int decodedAvailable = 0; private static final int LF = 10; private static final int CR = 13; private static final byte PADDING = (byte) '='; /** * @param in */ public Base64InputStream(final InputStream in) { super(in); } @Override public int available() throws IOException { return this.in.available() * 3 / 4 + this.decodedAvailable; } private void decodeBase64() throws IOException { int next = -1; int encodedPosition = 0; this.decodedAvailable = 0; while (encodedPosition < 4 && (next = this.in.read()) >= 0) { /* fill base64Encoded buffer */ if (next == Base64InputStream.LF || next == Base64InputStream.CR) { /* ignore CR and LF */ continue; } this.base64Encoded[encodedPosition++] = (byte) next; } if (next == -1 && encodedPosition != 0) { throw new IOException("Base64 encoding error"); } if (next == -1 && encodedPosition == 0) { return; } byte byte_part_1 = Base64InputStream.BASE64MAP[this.base64Encoded[0] & 0xff]; byte byte_part_2 = Base64InputStream.BASE64MAP[this.base64Encoded[1] & 0xff]; /* reconstruct first decoded byte */ this.base64Decoded[this.decodedAvailable++] = (byte) (byte_part_1 << 2 & 0xfc | byte_part_2 >>> 4 & 0x3); if (this.base64Encoded[2] != Base64InputStream.PADDING) { /* still more base64 data available */ byte_part_1 = byte_part_2; byte_part_2 = Base64InputStream.BASE64MAP[this.base64Encoded[2] & 0xff]; /* reconstruct next decoded byte */ this.base64Decoded[this.decodedAvailable++] = (byte) (byte_part_1 << 4 & 0xf0 | byte_part_2 >>> 2 & 0xf); if (this.base64Encoded[3] != Base64InputStream.PADDING) { /* still more base64 data available */ byte_part_1 = byte_part_2; byte_part_2 = Base64InputStream.BASE64MAP[this.base64Encoded[3] & 0xff]; this.base64Decoded[this.decodedAvailable++] = (byte) (byte_part_1 << 6 & 0xc0 | byte_part_2 & 0x3f); } } } @Override public synchronized void mark(final int readlimit) { } @Override public boolean markSupported() { return false; } @Override public int read() throws IOException { final int next = this.returnDecodedBase64(); if (next >= 0) { /* we still have decoded data left to return */ return next; } /* we have to decode some more data again */ this.decodeBase64(); /* return what is possible to return or EOF */ return this.returnDecodedBase64(); } @Override public int read(final byte[] b, final int off, final int len) throws IOException { /* taken from InputStream */ if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int c = this.read(); if (c == -1) { return -1; } int index = 0; b[off + index++] = (byte) c; for (; index < len; index++) { c = this.read(); if (c == -1) { break; } b[off + index] = (byte) c; } return index; } @Override public synchronized void reset() throws IOException { throw new IOException("mark/reset not supported"); } private int returnDecodedBase64() { int next = -1; if (this.decodedAvailable > 0) { this.decodedAvailable--; /*need to mask the byte*/ next = (int) ((byte) this.base64Decoded[0] & 0xff); this.base64Decoded[0] = this.base64Decoded[1]; this.base64Decoded[1] = this.base64Decoded[2]; } return next; } }