1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
|
/*
* MimeInputStream.java - A Filter stream for MIME encoding
*
* Copyright (C) 2000,,2003 2002 Matt Albrecht
* groboclown@users.sourceforge.net
* http://groboutils.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package net.sourceforge.groboutils.util.io.v1;
import java.io.FilterInputStream;
import java.io.InputStream;
import java.io.IOException;
/**
* java.io.FilterInputStream implementation for Mime base 64. Not incredibly
* efficient, but it works and is small.
*
* All we need to implement are:
* read(int)
* read( byte b[], int off, int len )
* skip( long n ) - for translating the # of bytes to skip into mime bytes (4-to-3 ratio)
* available() - for the same reason as skip
*
* @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
* @since 0.9.0 Alpha (early 2000)
* @version $Date: 2003/02/10 22:52:45 $
*/
public class MimeInputStream extends FilterInputStream
{
private int bits = 0, spare = 0;
/**
* Mime character set translation
*/
private static final int[] charset = {
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R',
'S','T','U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j',
'k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1',
'2','3','4','5','6','7','8','9','+','/' };
private static final int UPPER_START = 0;
private static final int LOWER_START = 26;
private static final int NUM_START = LOWER_START+26;
private static final int PLUS = NUM_START+10;
private static final int SLASH = PLUS+1;
private static final int pad = '=';
/** Constructor! */
public MimeInputStream( InputStream i )
{ super(i); }
/**
* Write the specified <code>byte</code>, performing mime encoding.
*
* <p>Override this method, since all other write methods call it.
*
* @exception IOException If an I/O error occurs
*/
public int read() throws IOException
{
int s;
int c, val;
// find the mime value - fast way doesn't loop through the mime charset
for (;;)
{
c = super.read();
if (c < 0) return c; // EOF
if (c >= 'A' && c <= 'Z') { val = c - 'A' + UPPER_START; }
else if (c >= 'a' && c <= 'z') { val = c - 'a' + LOWER_START; }
else if (c >= '0' && c <= '9') { val = c - '0' + NUM_START; }
else if (c == '+') { val = PLUS; }
else if (c == '/') { val = SLASH; }
else if (c == pad)
{ throw new IOException("end-of-mime character encountered"); }
else // ignore out-of-bounds characters, per specs
continue;
switch (bits)
{
case 0: bits++;
spare = val << 2;
// didn't get a full byte - continue the read
break;
case 1: bits++;
s = spare | ((val >> 4) & 0x03);
spare = (val << 4) & 0xF0;
return s;
case 2: bits++;
s = spare | ((val >> 2) & 0x0F);
spare = (val << 6) & 0xC0;
return s;
case 3: bits = 0;
// val is already masked (&) with 0x3f
return spare | val;
}
}
}
/**
* Reads up to <code>len</code> bytes of data from this input stream
* into an array of bytes. This method blocks until some input is
* available.
* <p>
* This method performs <code>in.read(b, off, len)</code>, translates the
* mime characters, and returns the result.
*
* @param b the buffer into which the data is read.
* @param off the start offset of the data.
* @param len the maximum number of bytes read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
public int read(byte b[], int off, int len) throws IOException
{
// size checking
if (b == null || b.length <= off || b.length <= off+len)
throw new IllegalArgumentException();
int s, i = off, j, sofar = 0;
int c, val,
ourlen;
byte buffer[];
// out-of-bounds values may throw us off the correct count
while (sofar < len)
{
j = 0;
ourlen = (len - sofar) * 4;
if ((ourlen % 3) != 0) { ourlen /= 3; ourlen++; }
else ourlen /= 3;
buffer = new byte[ ourlen ];
// translate the length to mime size
in.read( buffer, 0, ourlen );
// find the mime value - fast way doesn't loop through the mime charset
for (; j < ourlen; j++)
{
c = buffer[j];
if (c >= 'A' && c <= 'Z') { val = c - 'A' + UPPER_START; }
else if (c >= 'a' && c <= 'z') { val = c - 'a' + LOWER_START; }
else if (c >= '0' && c <= '9') { val = c - '0' + NUM_START; }
else if (c == '+') { val = PLUS; }
else if (c == '/') { val = SLASH; }
else if (c == pad)
{
// end of mime
b[i] = (byte)spare;
return sofar + j + 1;
}
else // ignore out-of-bounds characters, per specs
continue;
switch (bits)
{
case 0: bits++;
spare = val << 2;
// didn't get a full byte - continue the read
break;
case 1: bits++;
b[i++] = (byte)(spare | ((val >> 4) & 0x03));
spare = (val << 4) & 0x03;
break;
case 2: bits = 0;
// val is already masked (&) with 0x3f
b[i++] = (byte)(spare | val);
break;
}
} // end of for loop
sofar += j;
}
return sofar+1;
}
*/
/**
* Skips over and discards <code>n</code> bytes of data from the
* input stream. The <code>skip</code> method may, for a variety of
* reasons, end up skipping over some smaller number of bytes,
* possibly <code>0</code>. The actual number of bytes skipped is
* returned.
* <p>
* This method performs <code>in.skip(n)</code>, followed by a quick
* translation to mime size.
*
* @param n the number of bytes to be skipped.
* @return the actual number of bytes skipped.
* @exception IOException if an I/O error occurs.
*/
public long skip(long n) throws IOException
{
long p = n * 4;
if ((p % 3) != 0) { p /= 3; p++; }
else { p /= 3; }
p = in.skip(p);
p *= 3;
if ((p & 0x03) != 0) { p >>= 2; p++; }
else { p >>= 2; }
return p;
}
/**
* Returns the number of bytes that can be read from this input
* stream without blocking.
* <p>
* This method performs <code>in.available(n)</code>, does the mime-size
* conversion, and returns the result.
*
* @return the number of bytes that can be read from the input stream
* without blocking.
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
public int available() throws IOException
{
int p = in.available() * 3;
if ((p & 0x03) != 0) { p >>= 2; p++; }
else { p >>= 2; }
return p;
}
}
|