001: /*
002: * MimeInputStream.java - A Filter stream for MIME encoding
003: *
004: * Copyright (C) 2000,,2003 2002 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or (at your option) any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU Lesser General Public License for more details.
017: *
018: * You should have received a copy of the GNU Lesser General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
021: */
022:
023: package net.sourceforge.groboutils.util.io.v1;
024:
025: import java.io.FilterInputStream;
026: import java.io.InputStream;
027: import java.io.IOException;
028:
029: /**
030: * java.io.FilterInputStream implementation for Mime base 64. Not incredibly
031: * efficient, but it works and is small.
032: *
033: * All we need to implement are:
034: * read(int)
035: * read( byte b[], int off, int len )
036: * skip( long n ) - for translating the # of bytes to skip into mime bytes (4-to-3 ratio)
037: * available() - for the same reason as skip
038: *
039: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
040: * @since 0.9.0 Alpha (early 2000)
041: * @version $Date: 2003/02/10 22:52:45 $
042: */
043: public class MimeInputStream extends FilterInputStream {
044: private int bits = 0, spare = 0;
045:
046: /**
047: * Mime character set translation
048: */
049: private static final int[] charset = { 'A', 'B', 'C', 'D', 'E',
050: 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
051: 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
052: 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
053: 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
054: '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
055: private static final int UPPER_START = 0;
056: private static final int LOWER_START = 26;
057: private static final int NUM_START = LOWER_START + 26;
058: private static final int PLUS = NUM_START + 10;
059: private static final int SLASH = PLUS + 1;
060: private static final int pad = '=';
061:
062: /** Constructor! */
063: public MimeInputStream(InputStream i) {
064: super (i);
065: }
066:
067: /**
068: * Write the specified <code>byte</code>, performing mime encoding.
069: *
070: * <p>Override this method, since all other write methods call it.
071: *
072: * @exception IOException If an I/O error occurs
073: */
074: public int read() throws IOException {
075: int s;
076: int c, val;
077:
078: // find the mime value - fast way doesn't loop through the mime charset
079: for (;;) {
080: c = super .read();
081: if (c < 0)
082: return c; // EOF
083:
084: if (c >= 'A' && c <= 'Z') {
085: val = c - 'A' + UPPER_START;
086: } else if (c >= 'a' && c <= 'z') {
087: val = c - 'a' + LOWER_START;
088: } else if (c >= '0' && c <= '9') {
089: val = c - '0' + NUM_START;
090: } else if (c == '+') {
091: val = PLUS;
092: } else if (c == '/') {
093: val = SLASH;
094: } else if (c == pad) {
095: throw new IOException(
096: "end-of-mime character encountered");
097: } else
098: // ignore out-of-bounds characters, per specs
099: continue;
100: switch (bits) {
101: case 0:
102: bits++;
103: spare = val << 2;
104: // didn't get a full byte - continue the read
105: break;
106: case 1:
107: bits++;
108: s = spare | ((val >> 4) & 0x03);
109: spare = (val << 4) & 0xF0;
110: return s;
111: case 2:
112: bits++;
113: s = spare | ((val >> 2) & 0x0F);
114: spare = (val << 6) & 0xC0;
115: return s;
116: case 3:
117: bits = 0;
118: // val is already masked (&) with 0x3f
119: return spare | val;
120: }
121: }
122: }
123:
124: /**
125: * Reads up to <code>len</code> bytes of data from this input stream
126: * into an array of bytes. This method blocks until some input is
127: * available.
128: * <p>
129: * This method performs <code>in.read(b, off, len)</code>, translates the
130: * mime characters, and returns the result.
131: *
132: * @param b the buffer into which the data is read.
133: * @param off the start offset of the data.
134: * @param len the maximum number of bytes read.
135: * @return the total number of bytes read into the buffer, or
136: * <code>-1</code> if there is no more data because the end of
137: * the stream has been reached.
138: * @exception IOException if an I/O error occurs.
139: * @see java.io.FilterInputStream#in
140: public int read(byte b[], int off, int len) throws IOException
141: {
142: // size checking
143: if (b == null || b.length <= off || b.length <= off+len)
144: throw new IllegalArgumentException();
145:
146: int s, i = off, j, sofar = 0;
147: int c, val,
148: ourlen;
149: byte buffer[];
150:
151: // out-of-bounds values may throw us off the correct count
152: while (sofar < len)
153: {
154: j = 0;
155: ourlen = (len - sofar) * 4;
156: if ((ourlen % 3) != 0) { ourlen /= 3; ourlen++; }
157: else ourlen /= 3;
158:
159: buffer = new byte[ ourlen ];
160:
161: // translate the length to mime size
162: in.read( buffer, 0, ourlen );
163:
164: // find the mime value - fast way doesn't loop through the mime charset
165: for (; j < ourlen; j++)
166: {
167: c = buffer[j];
168: if (c >= 'A' && c <= 'Z') { val = c - 'A' + UPPER_START; }
169: else if (c >= 'a' && c <= 'z') { val = c - 'a' + LOWER_START; }
170: else if (c >= '0' && c <= '9') { val = c - '0' + NUM_START; }
171: else if (c == '+') { val = PLUS; }
172: else if (c == '/') { val = SLASH; }
173: else if (c == pad)
174: {
175: // end of mime
176: b[i] = (byte)spare;
177: return sofar + j + 1;
178: }
179: else // ignore out-of-bounds characters, per specs
180: continue;
181:
182: switch (bits)
183: {
184: case 0: bits++;
185: spare = val << 2;
186: // didn't get a full byte - continue the read
187: break;
188: case 1: bits++;
189: b[i++] = (byte)(spare | ((val >> 4) & 0x03));
190: spare = (val << 4) & 0x03;
191: break;
192: case 2: bits = 0;
193: // val is already masked (&) with 0x3f
194: b[i++] = (byte)(spare | val);
195: break;
196: }
197: } // end of for loop
198: sofar += j;
199: }
200: return sofar+1;
201: }
202: */
203:
204: /**
205: * Skips over and discards <code>n</code> bytes of data from the
206: * input stream. The <code>skip</code> method may, for a variety of
207: * reasons, end up skipping over some smaller number of bytes,
208: * possibly <code>0</code>. The actual number of bytes skipped is
209: * returned.
210: * <p>
211: * This method performs <code>in.skip(n)</code>, followed by a quick
212: * translation to mime size.
213: *
214: * @param n the number of bytes to be skipped.
215: * @return the actual number of bytes skipped.
216: * @exception IOException if an I/O error occurs.
217: */
218: public long skip(long n) throws IOException {
219: long p = n * 4;
220: if ((p % 3) != 0) {
221: p /= 3;
222: p++;
223: } else {
224: p /= 3;
225: }
226: p = in.skip(p);
227: p *= 3;
228: if ((p & 0x03) != 0) {
229: p >>= 2;
230: p++;
231: } else {
232: p >>= 2;
233: }
234: return p;
235: }
236:
237: /**
238: * Returns the number of bytes that can be read from this input
239: * stream without blocking.
240: * <p>
241: * This method performs <code>in.available(n)</code>, does the mime-size
242: * conversion, and returns the result.
243: *
244: * @return the number of bytes that can be read from the input stream
245: * without blocking.
246: * @exception IOException if an I/O error occurs.
247: * @see java.io.FilterInputStream#in
248: */
249: public int available() throws IOException {
250: int p = in.available() * 3;
251: if ((p & 0x03) != 0) {
252: p >>= 2;
253: p++;
254: } else {
255: p >>= 2;
256: }
257: return p;
258: }
259: }
|