1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the GNU General Public License v3+, or later.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc
.squirreljme
.plugin
.util
;
12 import java
.io
.ByteArrayOutputStream
;
13 import java
.io
.IOException
;
14 import java
.io
.InputStream
;
15 import java
.io
.Reader
;
16 import java
.io
.StringReader
;
17 import java
.util
.Arrays
;
20 * This decodes the base64 character set, ignoring invalid characters, and
21 * provides the binary data for the input. If the padding character is reached
22 * or if the input stream runs out of characters then EOF is triggered.
26 public final class Base64Decoder
29 /** The source reader. */
30 protected final Reader in
;
32 /** Ignore padding characters. */
33 protected final boolean ignorepadding
;
35 /** The alphabet to use for decoding. */
36 private final char[] _alphabet
;
38 /** The ASCII map for quick lookup. */
39 private final byte[] _ascii
;
41 /** Output bytes to drain. */
42 private final byte[] _drain
=
45 /** The current fill buffer. */
46 private volatile int _buffer
;
48 /** The number of bits which are in the buffer. */
49 private volatile int _bits
;
51 /** Has EOF been reached if the pad has been detected? */
52 private volatile boolean _readeof
;
54 /** The current output drain position. */
55 private volatile int _drained
=
58 /** The maximum value for drained values. */
59 private volatile int _drainedmax
=
63 * Initializes the decode the default MIME alphabet.
65 * @param __in The input set of characters.
66 * @throws NullPointerException On null arguments.
69 public Base64Decoder(Reader __in
)
71 this(__in
, Base64Alphabet
.BASIC
);
75 * Initializes the decoder using the specified alphabet.
77 * @param __in The input set of characters.
78 * @param __chars The pre-defined character set to use for the alphabet.
79 * @throws NullPointerException On null arguments.
82 public Base64Decoder(Reader __in
, Base64Alphabet __chars
)
83 throws NullPointerException
85 this(__in
, __chars
._alphabet
, false);
89 * Initializes the decoder using the specified custom alphabet.
91 * @param __in The input set of characters.
92 * @param __chars The characters to use for the alphabet.
93 * @throws IllegalArgumentException If the alphabet is of the incorrect
95 * @throws NullPointerException On null arguments.
98 public Base64Decoder(Reader __in
, String __chars
)
99 throws IllegalArgumentException
, NullPointerException
101 this(__in
, __chars
.toCharArray(), false);
105 * Initializes the decoder using the specified custom alphabet.
107 * @param __in The input set of characters.
108 * @param __chars The characters to use for the alphabet.
109 * @throws IllegalArgumentException If the alphabet is of the incorrect
111 * @throws NullPointerException On null arguments.
114 public Base64Decoder(Reader __in
, char[] __chars
)
115 throws IllegalArgumentException
, NullPointerException
117 this(__in
, __chars
, false);
121 * Initializes the decoder using the default alphabet.
123 * @param __in The input set of characters.
124 * @param __chars The pre-defined character set to use for the alphabet.
125 * @param __ip Ignore padding characters and do not treat them as the end
127 * @throws NullPointerException On null arguments.
130 public Base64Decoder(Reader __in
, Base64Alphabet __chars
, boolean __ip
)
131 throws NullPointerException
133 this(__in
, __chars
._alphabet
, __ip
);
137 * Initializes the decoder using the specified custom alphabet.
139 * @param __in The input set of characters.
140 * @param __chars The characters to use for the alphabet.
141 * @param __ip Ignore padding characters and do not treat them as the end
143 * @throws IllegalArgumentException If the alphabet is of the incorrect
145 * @throws NullPointerException On null arguments.
148 public Base64Decoder(Reader __in
, String __chars
, boolean __ip
)
149 throws IllegalArgumentException
, NullPointerException
151 this(__in
, __chars
.toCharArray(), __ip
);
155 * Initializes the decoder using the specified custom alphabet.
157 * @param __in The input set of characters.
158 * @param __chars The characters to use for the alphabet.
159 * @param __ip Ignore padding characters and do not treat them as the end
161 * @throws IllegalArgumentException If the alphabet is of the incorrect
163 * @throws NullPointerException On null arguments.
166 public Base64Decoder(Reader __in
, char[] __chars
, boolean __ip
)
167 throws IllegalArgumentException
, NullPointerException
169 if (__in
== null || __chars
== null)
170 throw new NullPointerException("NARG");
172 // {@squirreljme.error BD0g The alphabet to use for the base64
173 // decoder must be 64 characters plus one padding character.
174 // (The character count)}
176 if ((n
= __chars
.length
) != 65)
177 throw new IllegalArgumentException(String
.format("BD0g %d", n
));
181 this.ignorepadding
= __ip
;
182 this._alphabet
= (__chars
= __chars
.clone());
184 // Build ASCII map for quick in-range character lookup
185 byte[] ascii
= new byte[128];
186 Arrays
.fill(ascii
, (byte)-1);
187 for (int i
= 0; i
< 65; i
++)
201 public final int available()
204 int drained
= this._drained
;
206 // There are bytes which are ready and in the drain that we do not
207 // need to block reading them?
209 return this._drainedmax
- drained
;
218 public final void close()
229 public final int read()
232 // If there is stuff to be drained, quickly drain that so we do not
233 // need to go deeper into the heavier method
234 int drained
= this._drained
;
237 // Read in drained character
238 int rv
= this._drain
[drained
++] & 0xFF;
240 // Reached the drain limit?
241 if (drained
== this._drainedmax
)
244 this._drainedmax
= -1;
247 // Would still be drain
249 this._drained
= drained
;
255 // Previously read EOF, so this will just return EOF
259 // Otherwise decode and read
260 byte[] next
= new byte[1];
263 int rc
= this.read(next
, 0, 1);
273 return (next
[0] & 0xFF);
282 public final int read(byte[] __b
)
283 throws IOException
, NullPointerException
286 throw new NullPointerException("NARG");
288 return this.read(__b
, 0, __b
.length
);
296 public final int read(byte[] __b
, int __o
, int __l
)
297 throws IndexOutOfBoundsException
, IOException
, NullPointerException
300 throw new NullPointerException("NARG");
301 if (__o
< 0 || __l
< 0 || (__o
+ __l
) < 0 || (__o
+ __l
) > __b
.length
)
302 throw new IndexOutOfBoundsException("IOOB");
304 // Did a previous read cause a padded EOF?
305 boolean readeof
= this._readeof
;
309 boolean ignorepadding
= this.ignorepadding
;
310 char[] alphabet
= this._alphabet
;
311 byte[] ascii
= this._ascii
;
312 byte[] drain
= this._drain
;
314 // This buffer is filled into as needed when input characters are read
315 int buffer
= this._buffer
,
317 drained
= this._drained
,
318 drainedmax
= this._drainedmax
;
320 // Keep trying to fill bytes in
324 // Still need to drain bytes away
325 if (drained
!= -1 && drained
< drainedmax
)
328 __b
[__o
++] = drain
[drained
++];
331 // Drained all the characters
332 if (drained
== drainedmax
)
333 drained
= drainedmax
= -1;
344 // Read in character and decode it
350 // {@squirreljme.error BD01 Read EOF from input when there
351 // were expected to be more characters or the ending padding
352 // character. (The bits in the buffer)}
354 throw new IOException("BD01 " + bits
);
361 // Determine the value of the character
367 for (int i
= 0; i
< 65; i
++)
368 if (i
== alphabet
[i
])
375 // Invalid, ignore and continue
376 if (ch
== -1 || (ignorepadding
&& ch
== 64))
379 // Decoded padding character
382 // {@squirreljme.error BD02 Did not expect a padding character.
383 // (The number of decoded bits in queue)}
384 if (bits
== 0 || bits
== 24)
385 throw new IOException("BD02 " + bits
);
387 // Only want to store a single extra byte since that is
391 // {@squirreljme.error BD03 Expected another padding
393 if (in
.read() != alphabet
[64])
394 throw new IOException("BD03");
396 drain
[0] = (byte)(buffer
>>> 4);
401 // Otherwise there will be two characters to drain
404 drain
[0] = (byte)(buffer
>>> 10);
405 drain
[1] = (byte)(buffer
>>> 2);
428 // Drain and empty the buffer
432 drain
[0] = (byte)(buffer
>>> 16);
433 drain
[1] = (byte)(buffer
>>> 8);
434 drain
[2] = (byte)buffer
;
436 // Set these to drain
446 // Store state for next run
447 this._buffer
= buffer
;
449 this._readeof
= readeof
;
450 this._drained
= drained
;
451 this._drainedmax
= drainedmax
;
453 // Return the read count
454 if (readeof
&& rv
== 0)
460 * Decodes the input string to byte values.
462 * @param __in The string to decode.
463 * @param __ab The alphabet to use.
464 * @return The resulting byte array.
465 * @throws IllegalArgumentException If the input string is not valid.
466 * @throws NullPointerException On null arguments.
469 public static final byte[] decode(String __in
, Base64Alphabet __ab
)
470 throws IllegalArgumentException
, NullPointerException
472 return Base64Decoder
.decode(__in
, __ab
, false);
476 * Decodes the input string to byte values.
478 * @param __in The string to decode.
479 * @param __ab The alphabet to use.
480 * @param __ip Is padding ignored?
481 * @return The resulting byte array.
482 * @throws IllegalArgumentException If the input string is not valid.
483 * @throws NullPointerException On null arguments.
486 public static final byte[] decode(String __in
, Base64Alphabet __ab
,
488 throws IllegalArgumentException
, NullPointerException
490 if (__in
== null || __ab
== null)
491 throw new NullPointerException("NARG");
493 // Wrap in a reader to decode
494 try (ByteArrayOutputStream baos
= new ByteArrayOutputStream())
496 byte[] buf
= new byte[32];
499 try (InputStream in
= new Base64Decoder(
500 new StringReader(__in
), __ab
, __ip
))
504 int rc
= in
.read(buf
);
511 baos
.write(buf
, 0, rc
);
515 // Return resulting byte array
516 return baos
.toByteArray();
519 // {@squirreljme.error BD04 Could not decode the input string.}
520 catch (IOException e
)
522 throw new IllegalArgumentException("BD04", e
);