Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/util/Base64.java
38829 views
1
/*
2
* Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package java.util;
27
28
import java.io.FilterOutputStream;
29
import java.io.InputStream;
30
import java.io.IOException;
31
import java.io.OutputStream;
32
import java.nio.ByteBuffer;
33
import java.nio.charset.StandardCharsets;
34
35
/**
36
* This class consists exclusively of static methods for obtaining
37
* encoders and decoders for the Base64 encoding scheme. The
38
* implementation of this class supports the following types of Base64
39
* as specified in
40
* <a href="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</a> and
41
* <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
42
*
43
* <ul>
44
* <li><a name="basic"><b>Basic</b></a>
45
* <p> Uses "The Base64 Alphabet" as specified in Table 1 of
46
* RFC 4648 and RFC 2045 for encoding and decoding operation.
47
* The encoder does not add any line feed (line separator)
48
* character. The decoder rejects data that contains characters
49
* outside the base64 alphabet.</p></li>
50
*
51
* <li><a name="url"><b>URL and Filename safe</b></a>
52
* <p> Uses the "URL and Filename safe Base64 Alphabet" as specified
53
* in Table 2 of RFC 4648 for encoding and decoding. The
54
* encoder does not add any line feed (line separator) character.
55
* The decoder rejects data that contains characters outside the
56
* base64 alphabet.</p></li>
57
*
58
* <li><a name="mime"><b>MIME</b></a>
59
* <p> Uses the "The Base64 Alphabet" as specified in Table 1 of
60
* RFC 2045 for encoding and decoding operation. The encoded output
61
* must be represented in lines of no more than 76 characters each
62
* and uses a carriage return {@code '\r'} followed immediately by
63
* a linefeed {@code '\n'} as the line separator. No line separator
64
* is added to the end of the encoded output. All line separators
65
* or other characters not found in the base64 alphabet table are
66
* ignored in decoding operation.</p></li>
67
* </ul>
68
*
69
* <p> Unless otherwise noted, passing a {@code null} argument to a
70
* method of this class will cause a {@link java.lang.NullPointerException
71
* NullPointerException} to be thrown.
72
*
73
* @author Xueming Shen
74
* @since 1.8
75
*/
76
77
public class Base64 {
78
79
private Base64() {}
80
81
/**
82
* Returns a {@link Encoder} that encodes using the
83
* <a href="#basic">Basic</a> type base64 encoding scheme.
84
*
85
* @return A Base64 encoder.
86
*/
87
public static Encoder getEncoder() {
88
return Encoder.RFC4648;
89
}
90
91
/**
92
* Returns a {@link Encoder} that encodes using the
93
* <a href="#url">URL and Filename safe</a> type base64
94
* encoding scheme.
95
*
96
* @return A Base64 encoder.
97
*/
98
public static Encoder getUrlEncoder() {
99
return Encoder.RFC4648_URLSAFE;
100
}
101
102
/**
103
* Returns a {@link Encoder} that encodes using the
104
* <a href="#mime">MIME</a> type base64 encoding scheme.
105
*
106
* @return A Base64 encoder.
107
*/
108
public static Encoder getMimeEncoder() {
109
return Encoder.RFC2045;
110
}
111
112
/**
113
* Returns a {@link Encoder} that encodes using the
114
* <a href="#mime">MIME</a> type base64 encoding scheme
115
* with specified line length and line separators.
116
*
117
* @param lineLength
118
* the length of each output line (rounded down to nearest multiple
119
* of 4). If {@code lineLength <= 0} the output will not be separated
120
* in lines
121
* @param lineSeparator
122
* the line separator for each output line
123
*
124
* @return A Base64 encoder.
125
*
126
* @throws IllegalArgumentException if {@code lineSeparator} includes any
127
* character of "The Base64 Alphabet" as specified in Table 1 of
128
* RFC 2045.
129
*/
130
public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) {
131
Objects.requireNonNull(lineSeparator);
132
int[] base64 = Decoder.fromBase64;
133
for (byte b : lineSeparator) {
134
if (base64[b & 0xff] != -1)
135
throw new IllegalArgumentException(
136
"Illegal base64 line separator character 0x" + Integer.toString(b, 16));
137
}
138
if (lineLength <= 0) {
139
return Encoder.RFC4648;
140
}
141
return new Encoder(false, lineSeparator, lineLength >> 2 << 2, true);
142
}
143
144
/**
145
* Returns a {@link Decoder} that decodes using the
146
* <a href="#basic">Basic</a> type base64 encoding scheme.
147
*
148
* @return A Base64 decoder.
149
*/
150
public static Decoder getDecoder() {
151
return Decoder.RFC4648;
152
}
153
154
/**
155
* Returns a {@link Decoder} that decodes using the
156
* <a href="#url">URL and Filename safe</a> type base64
157
* encoding scheme.
158
*
159
* @return A Base64 decoder.
160
*/
161
public static Decoder getUrlDecoder() {
162
return Decoder.RFC4648_URLSAFE;
163
}
164
165
/**
166
* Returns a {@link Decoder} that decodes using the
167
* <a href="#mime">MIME</a> type base64 decoding scheme.
168
*
169
* @return A Base64 decoder.
170
*/
171
public static Decoder getMimeDecoder() {
172
return Decoder.RFC2045;
173
}
174
175
/**
176
* This class implements an encoder for encoding byte data using
177
* the Base64 encoding scheme as specified in RFC 4648 and RFC 2045.
178
*
179
* <p> Instances of {@link Encoder} class are safe for use by
180
* multiple concurrent threads.
181
*
182
* <p> Unless otherwise noted, passing a {@code null} argument to
183
* a method of this class will cause a
184
* {@link java.lang.NullPointerException NullPointerException} to
185
* be thrown.
186
*
187
* @see Decoder
188
* @since 1.8
189
*/
190
public static class Encoder {
191
192
private final byte[] newline;
193
private final int linemax;
194
private final boolean isURL;
195
private final boolean doPadding;
196
197
private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) {
198
this.isURL = isURL;
199
this.newline = newline;
200
this.linemax = linemax;
201
this.doPadding = doPadding;
202
}
203
204
/**
205
* This array is a lookup table that translates 6-bit positive integer
206
* index values into their "Base64 Alphabet" equivalents as specified
207
* in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648).
208
*/
209
private static final char[] toBase64 = {
210
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
211
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
212
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
213
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
214
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
215
};
216
217
/**
218
* It's the lookup table for "URL and Filename safe Base64" as specified
219
* in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and
220
* '_'. This table is used when BASE64_URL is specified.
221
*/
222
private static final char[] toBase64URL = {
223
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
224
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
225
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
226
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
227
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
228
};
229
230
private static final int MIMELINEMAX = 76;
231
private static final byte[] CRLF = new byte[] {'\r', '\n'};
232
233
static final Encoder RFC4648 = new Encoder(false, null, -1, true);
234
static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true);
235
static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true);
236
237
private final int outLength(int srclen) {
238
int len = 0;
239
if (doPadding) {
240
len = 4 * ((srclen + 2) / 3);
241
} else {
242
int n = srclen % 3;
243
len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1);
244
}
245
if (linemax > 0) // line separators
246
len += (len - 1) / linemax * newline.length;
247
return len;
248
}
249
250
/**
251
* Encodes all bytes from the specified byte array into a newly-allocated
252
* byte array using the {@link Base64} encoding scheme. The returned byte
253
* array is of the length of the resulting bytes.
254
*
255
* @param src
256
* the byte array to encode
257
* @return A newly-allocated byte array containing the resulting
258
* encoded bytes.
259
*/
260
public byte[] encode(byte[] src) {
261
int len = outLength(src.length); // dst array size
262
byte[] dst = new byte[len];
263
int ret = encode0(src, 0, src.length, dst);
264
if (ret != dst.length)
265
return Arrays.copyOf(dst, ret);
266
return dst;
267
}
268
269
/**
270
* Encodes all bytes from the specified byte array using the
271
* {@link Base64} encoding scheme, writing the resulting bytes to the
272
* given output byte array, starting at offset 0.
273
*
274
* <p> It is the responsibility of the invoker of this method to make
275
* sure the output byte array {@code dst} has enough space for encoding
276
* all bytes from the input byte array. No bytes will be written to the
277
* output byte array if the output byte array is not big enough.
278
*
279
* @param src
280
* the byte array to encode
281
* @param dst
282
* the output byte array
283
* @return The number of bytes written to the output byte array
284
*
285
* @throws IllegalArgumentException if {@code dst} does not have enough
286
* space for encoding all input bytes.
287
*/
288
public int encode(byte[] src, byte[] dst) {
289
int len = outLength(src.length); // dst array size
290
if (dst.length < len)
291
throw new IllegalArgumentException(
292
"Output byte array is too small for encoding all input bytes");
293
return encode0(src, 0, src.length, dst);
294
}
295
296
/**
297
* Encodes the specified byte array into a String using the {@link Base64}
298
* encoding scheme.
299
*
300
* <p> This method first encodes all input bytes into a base64 encoded
301
* byte array and then constructs a new String by using the encoded byte
302
* array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1
303
* ISO-8859-1} charset.
304
*
305
* <p> In other words, an invocation of this method has exactly the same
306
* effect as invoking
307
* {@code new String(encode(src), StandardCharsets.ISO_8859_1)}.
308
*
309
* @param src
310
* the byte array to encode
311
* @return A String containing the resulting Base64 encoded characters
312
*/
313
@SuppressWarnings("deprecation")
314
public String encodeToString(byte[] src) {
315
byte[] encoded = encode(src);
316
return new String(encoded, 0, 0, encoded.length);
317
}
318
319
/**
320
* Encodes all remaining bytes from the specified byte buffer into
321
* a newly-allocated ByteBuffer using the {@link Base64} encoding
322
* scheme.
323
*
324
* Upon return, the source buffer's position will be updated to
325
* its limit; its limit will not have been changed. The returned
326
* output buffer's position will be zero and its limit will be the
327
* number of resulting encoded bytes.
328
*
329
* @param buffer
330
* the source ByteBuffer to encode
331
* @return A newly-allocated byte buffer containing the encoded bytes.
332
*/
333
public ByteBuffer encode(ByteBuffer buffer) {
334
int len = outLength(buffer.remaining());
335
byte[] dst = new byte[len];
336
int ret = 0;
337
if (buffer.hasArray()) {
338
ret = encode0(buffer.array(),
339
buffer.arrayOffset() + buffer.position(),
340
buffer.arrayOffset() + buffer.limit(),
341
dst);
342
buffer.position(buffer.limit());
343
} else {
344
byte[] src = new byte[buffer.remaining()];
345
buffer.get(src);
346
ret = encode0(src, 0, src.length, dst);
347
}
348
if (ret != dst.length)
349
dst = Arrays.copyOf(dst, ret);
350
return ByteBuffer.wrap(dst);
351
}
352
353
/**
354
* Wraps an output stream for encoding byte data using the {@link Base64}
355
* encoding scheme.
356
*
357
* <p> It is recommended to promptly close the returned output stream after
358
* use, during which it will flush all possible leftover bytes to the underlying
359
* output stream. Closing the returned output stream will close the underlying
360
* output stream.
361
*
362
* @param os
363
* the output stream.
364
* @return the output stream for encoding the byte data into the
365
* specified Base64 encoded format
366
*/
367
public OutputStream wrap(OutputStream os) {
368
Objects.requireNonNull(os);
369
return new EncOutputStream(os, isURL ? toBase64URL : toBase64,
370
newline, linemax, doPadding);
371
}
372
373
/**
374
* Returns an encoder instance that encodes equivalently to this one,
375
* but without adding any padding character at the end of the encoded
376
* byte data.
377
*
378
* <p> The encoding scheme of this encoder instance is unaffected by
379
* this invocation. The returned encoder instance should be used for
380
* non-padding encoding operation.
381
*
382
* @return an equivalent encoder that encodes without adding any
383
* padding character at the end
384
*/
385
public Encoder withoutPadding() {
386
if (!doPadding)
387
return this;
388
return new Encoder(isURL, newline, linemax, false);
389
}
390
391
private int encode0(byte[] src, int off, int end, byte[] dst) {
392
char[] base64 = isURL ? toBase64URL : toBase64;
393
int sp = off;
394
int slen = (end - off) / 3 * 3;
395
int sl = off + slen;
396
if (linemax > 0 && slen > linemax / 4 * 3)
397
slen = linemax / 4 * 3;
398
int dp = 0;
399
while (sp < sl) {
400
int sl0 = Math.min(sp + slen, sl);
401
for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
402
int bits = (src[sp0++] & 0xff) << 16 |
403
(src[sp0++] & 0xff) << 8 |
404
(src[sp0++] & 0xff);
405
dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
406
dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
407
dst[dp0++] = (byte)base64[(bits >>> 6) & 0x3f];
408
dst[dp0++] = (byte)base64[bits & 0x3f];
409
}
410
int dlen = (sl0 - sp) / 3 * 4;
411
dp += dlen;
412
sp = sl0;
413
if (dlen == linemax && sp < end) {
414
for (byte b : newline){
415
dst[dp++] = b;
416
}
417
}
418
}
419
if (sp < end) { // 1 or 2 leftover bytes
420
int b0 = src[sp++] & 0xff;
421
dst[dp++] = (byte)base64[b0 >> 2];
422
if (sp == end) {
423
dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];
424
if (doPadding) {
425
dst[dp++] = '=';
426
dst[dp++] = '=';
427
}
428
} else {
429
int b1 = src[sp++] & 0xff;
430
dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
431
dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];
432
if (doPadding) {
433
dst[dp++] = '=';
434
}
435
}
436
}
437
return dp;
438
}
439
}
440
441
/**
442
* This class implements a decoder for decoding byte data using the
443
* Base64 encoding scheme as specified in RFC 4648 and RFC 2045.
444
*
445
* <p> The Base64 padding character {@code '='} is accepted and
446
* interpreted as the end of the encoded byte data, but is not
447
* required. So if the final unit of the encoded byte data only has
448
* two or three Base64 characters (without the corresponding padding
449
* character(s) padded), they are decoded as if followed by padding
450
* character(s). If there is a padding character present in the
451
* final unit, the correct number of padding character(s) must be
452
* present, otherwise {@code IllegalArgumentException} (
453
* {@code IOException} when reading from a Base64 stream) is thrown
454
* during decoding.
455
*
456
* <p> Instances of {@link Decoder} class are safe for use by
457
* multiple concurrent threads.
458
*
459
* <p> Unless otherwise noted, passing a {@code null} argument to
460
* a method of this class will cause a
461
* {@link java.lang.NullPointerException NullPointerException} to
462
* be thrown.
463
*
464
* @see Encoder
465
* @since 1.8
466
*/
467
public static class Decoder {
468
469
private final boolean isURL;
470
private final boolean isMIME;
471
472
private Decoder(boolean isURL, boolean isMIME) {
473
this.isURL = isURL;
474
this.isMIME = isMIME;
475
}
476
477
/**
478
* Lookup table for decoding unicode characters drawn from the
479
* "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into
480
* their 6-bit positive integer equivalents. Characters that
481
* are not in the Base64 alphabet but fall within the bounds of
482
* the array are encoded to -1.
483
*
484
*/
485
private static final int[] fromBase64 = new int[256];
486
static {
487
Arrays.fill(fromBase64, -1);
488
for (int i = 0; i < Encoder.toBase64.length; i++)
489
fromBase64[Encoder.toBase64[i]] = i;
490
fromBase64['='] = -2;
491
}
492
493
/**
494
* Lookup table for decoding "URL and Filename safe Base64 Alphabet"
495
* as specified in Table2 of the RFC 4648.
496
*/
497
private static final int[] fromBase64URL = new int[256];
498
499
static {
500
Arrays.fill(fromBase64URL, -1);
501
for (int i = 0; i < Encoder.toBase64URL.length; i++)
502
fromBase64URL[Encoder.toBase64URL[i]] = i;
503
fromBase64URL['='] = -2;
504
}
505
506
static final Decoder RFC4648 = new Decoder(false, false);
507
static final Decoder RFC4648_URLSAFE = new Decoder(true, false);
508
static final Decoder RFC2045 = new Decoder(false, true);
509
510
/**
511
* Decodes all bytes from the input byte array using the {@link Base64}
512
* encoding scheme, writing the results into a newly-allocated output
513
* byte array. The returned byte array is of the length of the resulting
514
* bytes.
515
*
516
* @param src
517
* the byte array to decode
518
*
519
* @return A newly-allocated byte array containing the decoded bytes.
520
*
521
* @throws IllegalArgumentException
522
* if {@code src} is not in valid Base64 scheme
523
*/
524
public byte[] decode(byte[] src) {
525
byte[] dst = new byte[outLength(src, 0, src.length)];
526
int ret = decode0(src, 0, src.length, dst);
527
if (ret != dst.length) {
528
dst = Arrays.copyOf(dst, ret);
529
}
530
return dst;
531
}
532
533
/**
534
* Decodes a Base64 encoded String into a newly-allocated byte array
535
* using the {@link Base64} encoding scheme.
536
*
537
* <p> An invocation of this method has exactly the same effect as invoking
538
* {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))}
539
*
540
* @param src
541
* the string to decode
542
*
543
* @return A newly-allocated byte array containing the decoded bytes.
544
*
545
* @throws IllegalArgumentException
546
* if {@code src} is not in valid Base64 scheme
547
*/
548
public byte[] decode(String src) {
549
return decode(src.getBytes(StandardCharsets.ISO_8859_1));
550
}
551
552
/**
553
* Decodes all bytes from the input byte array using the {@link Base64}
554
* encoding scheme, writing the results into the given output byte array,
555
* starting at offset 0.
556
*
557
* <p> It is the responsibility of the invoker of this method to make
558
* sure the output byte array {@code dst} has enough space for decoding
559
* all bytes from the input byte array. No bytes will be be written to
560
* the output byte array if the output byte array is not big enough.
561
*
562
* <p> If the input byte array is not in valid Base64 encoding scheme
563
* then some bytes may have been written to the output byte array before
564
* IllegalargumentException is thrown.
565
*
566
* @param src
567
* the byte array to decode
568
* @param dst
569
* the output byte array
570
*
571
* @return The number of bytes written to the output byte array
572
*
573
* @throws IllegalArgumentException
574
* if {@code src} is not in valid Base64 scheme, or {@code dst}
575
* does not have enough space for decoding all input bytes.
576
*/
577
public int decode(byte[] src, byte[] dst) {
578
int len = outLength(src, 0, src.length);
579
if (dst.length < len)
580
throw new IllegalArgumentException(
581
"Output byte array is too small for decoding all input bytes");
582
return decode0(src, 0, src.length, dst);
583
}
584
585
/**
586
* Decodes all bytes from the input byte buffer using the {@link Base64}
587
* encoding scheme, writing the results into a newly-allocated ByteBuffer.
588
*
589
* <p> Upon return, the source buffer's position will be updated to
590
* its limit; its limit will not have been changed. The returned
591
* output buffer's position will be zero and its limit will be the
592
* number of resulting decoded bytes
593
*
594
* <p> {@code IllegalArgumentException} is thrown if the input buffer
595
* is not in valid Base64 encoding scheme. The position of the input
596
* buffer will not be advanced in this case.
597
*
598
* @param buffer
599
* the ByteBuffer to decode
600
*
601
* @return A newly-allocated byte buffer containing the decoded bytes
602
*
603
* @throws IllegalArgumentException
604
* if {@code src} is not in valid Base64 scheme.
605
*/
606
public ByteBuffer decode(ByteBuffer buffer) {
607
int pos0 = buffer.position();
608
try {
609
byte[] src;
610
int sp, sl;
611
if (buffer.hasArray()) {
612
src = buffer.array();
613
sp = buffer.arrayOffset() + buffer.position();
614
sl = buffer.arrayOffset() + buffer.limit();
615
buffer.position(buffer.limit());
616
} else {
617
src = new byte[buffer.remaining()];
618
buffer.get(src);
619
sp = 0;
620
sl = src.length;
621
}
622
byte[] dst = new byte[outLength(src, sp, sl)];
623
return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst));
624
} catch (IllegalArgumentException iae) {
625
buffer.position(pos0);
626
throw iae;
627
}
628
}
629
630
/**
631
* Returns an input stream for decoding {@link Base64} encoded byte stream.
632
*
633
* <p> The {@code read} methods of the returned {@code InputStream} will
634
* throw {@code IOException} when reading bytes that cannot be decoded.
635
*
636
* <p> Closing the returned input stream will close the underlying
637
* input stream.
638
*
639
* @param is
640
* the input stream
641
*
642
* @return the input stream for decoding the specified Base64 encoded
643
* byte stream
644
*/
645
public InputStream wrap(InputStream is) {
646
Objects.requireNonNull(is);
647
return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME);
648
}
649
650
private int outLength(byte[] src, int sp, int sl) {
651
int[] base64 = isURL ? fromBase64URL : fromBase64;
652
int paddings = 0;
653
int len = sl - sp;
654
if (len == 0)
655
return 0;
656
if (len < 2) {
657
if (isMIME && base64[0] == -1)
658
return 0;
659
throw new IllegalArgumentException(
660
"Input byte[] should at least have 2 bytes for base64 bytes");
661
}
662
if (isMIME) {
663
// scan all bytes to fill out all non-alphabet. a performance
664
// trade-off of pre-scan or Arrays.copyOf
665
int n = 0;
666
while (sp < sl) {
667
int b = src[sp++] & 0xff;
668
if (b == '=') {
669
len -= (sl - sp + 1);
670
break;
671
}
672
if ((b = base64[b]) == -1)
673
n++;
674
}
675
len -= n;
676
} else {
677
if (src[sl - 1] == '=') {
678
paddings++;
679
if (src[sl - 2] == '=')
680
paddings++;
681
}
682
}
683
if (paddings == 0 && (len & 0x3) != 0)
684
paddings = 4 - (len & 0x3);
685
return 3 * ((len + 3) / 4) - paddings;
686
}
687
688
private int decode0(byte[] src, int sp, int sl, byte[] dst) {
689
int[] base64 = isURL ? fromBase64URL : fromBase64;
690
int dp = 0;
691
int bits = 0;
692
int shiftto = 18; // pos of first byte of 4-byte atom
693
while (sp < sl) {
694
int b = src[sp++] & 0xff;
695
if ((b = base64[b]) < 0) {
696
if (b == -2) { // padding byte '='
697
// = shiftto==18 unnecessary padding
698
// x= shiftto==12 a dangling single x
699
// x to be handled together with non-padding case
700
// xx= shiftto==6&&sp==sl missing last =
701
// xx=y shiftto==6 last is not =
702
if (shiftto == 6 && (sp == sl || src[sp++] != '=') ||
703
shiftto == 18) {
704
throw new IllegalArgumentException(
705
"Input byte array has wrong 4-byte ending unit");
706
}
707
break;
708
}
709
if (isMIME) // skip if for rfc2045
710
continue;
711
else
712
throw new IllegalArgumentException(
713
"Illegal base64 character " +
714
Integer.toString(src[sp - 1], 16));
715
}
716
bits |= (b << shiftto);
717
shiftto -= 6;
718
if (shiftto < 0) {
719
dst[dp++] = (byte)(bits >> 16);
720
dst[dp++] = (byte)(bits >> 8);
721
dst[dp++] = (byte)(bits);
722
shiftto = 18;
723
bits = 0;
724
}
725
}
726
// reached end of byte array or hit padding '=' characters.
727
if (shiftto == 6) {
728
dst[dp++] = (byte)(bits >> 16);
729
} else if (shiftto == 0) {
730
dst[dp++] = (byte)(bits >> 16);
731
dst[dp++] = (byte)(bits >> 8);
732
} else if (shiftto == 12) {
733
// dangling single "x", incorrectly encoded.
734
throw new IllegalArgumentException(
735
"Last unit does not have enough valid bits");
736
}
737
// anything left is invalid, if is not MIME.
738
// if MIME, ignore all non-base64 character
739
while (sp < sl) {
740
if (isMIME && base64[src[sp++]] < 0)
741
continue;
742
throw new IllegalArgumentException(
743
"Input byte array has incorrect ending byte at " + sp);
744
}
745
return dp;
746
}
747
}
748
749
/*
750
* An output stream for encoding bytes into the Base64.
751
*/
752
private static class EncOutputStream extends FilterOutputStream {
753
754
private int leftover = 0;
755
private int b0, b1, b2;
756
private boolean closed = false;
757
758
private final char[] base64; // byte->base64 mapping
759
private final byte[] newline; // line separator, if needed
760
private final int linemax;
761
private final boolean doPadding;// whether or not to pad
762
private int linepos = 0;
763
764
EncOutputStream(OutputStream os, char[] base64,
765
byte[] newline, int linemax, boolean doPadding) {
766
super(os);
767
this.base64 = base64;
768
this.newline = newline;
769
this.linemax = linemax;
770
this.doPadding = doPadding;
771
}
772
773
@Override
774
public void write(int b) throws IOException {
775
byte[] buf = new byte[1];
776
buf[0] = (byte)(b & 0xff);
777
write(buf, 0, 1);
778
}
779
780
private void checkNewline() throws IOException {
781
if (linepos == linemax) {
782
out.write(newline);
783
linepos = 0;
784
}
785
}
786
787
@Override
788
public void write(byte[] b, int off, int len) throws IOException {
789
if (closed)
790
throw new IOException("Stream is closed");
791
if (off < 0 || len < 0 || len > b.length - off)
792
throw new ArrayIndexOutOfBoundsException();
793
if (len == 0)
794
return;
795
if (leftover != 0) {
796
if (leftover == 1) {
797
b1 = b[off++] & 0xff;
798
len--;
799
if (len == 0) {
800
leftover++;
801
return;
802
}
803
}
804
b2 = b[off++] & 0xff;
805
len--;
806
checkNewline();
807
out.write(base64[b0 >> 2]);
808
out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
809
out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]);
810
out.write(base64[b2 & 0x3f]);
811
linepos += 4;
812
}
813
int nBits24 = len / 3;
814
leftover = len - (nBits24 * 3);
815
while (nBits24-- > 0) {
816
checkNewline();
817
int bits = (b[off++] & 0xff) << 16 |
818
(b[off++] & 0xff) << 8 |
819
(b[off++] & 0xff);
820
out.write(base64[(bits >>> 18) & 0x3f]);
821
out.write(base64[(bits >>> 12) & 0x3f]);
822
out.write(base64[(bits >>> 6) & 0x3f]);
823
out.write(base64[bits & 0x3f]);
824
linepos += 4;
825
}
826
if (leftover == 1) {
827
b0 = b[off++] & 0xff;
828
} else if (leftover == 2) {
829
b0 = b[off++] & 0xff;
830
b1 = b[off++] & 0xff;
831
}
832
}
833
834
@Override
835
public void close() throws IOException {
836
if (!closed) {
837
closed = true;
838
if (leftover == 1) {
839
checkNewline();
840
out.write(base64[b0 >> 2]);
841
out.write(base64[(b0 << 4) & 0x3f]);
842
if (doPadding) {
843
out.write('=');
844
out.write('=');
845
}
846
} else if (leftover == 2) {
847
checkNewline();
848
out.write(base64[b0 >> 2]);
849
out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
850
out.write(base64[(b1 << 2) & 0x3f]);
851
if (doPadding) {
852
out.write('=');
853
}
854
}
855
leftover = 0;
856
out.close();
857
}
858
}
859
}
860
861
/*
862
* An input stream for decoding Base64 bytes
863
*/
864
private static class DecInputStream extends InputStream {
865
866
private final InputStream is;
867
private final boolean isMIME;
868
private final int[] base64; // base64 -> byte mapping
869
private int bits = 0; // 24-bit buffer for decoding
870
private int nextin = 18; // next available "off" in "bits" for input;
871
// -> 18, 12, 6, 0
872
private int nextout = -8; // next available "off" in "bits" for output;
873
// -> 8, 0, -8 (no byte for output)
874
private boolean eof = false;
875
private boolean closed = false;
876
877
DecInputStream(InputStream is, int[] base64, boolean isMIME) {
878
this.is = is;
879
this.base64 = base64;
880
this.isMIME = isMIME;
881
}
882
883
private byte[] sbBuf = new byte[1];
884
885
@Override
886
public int read() throws IOException {
887
return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff;
888
}
889
890
@Override
891
public int read(byte[] b, int off, int len) throws IOException {
892
if (closed)
893
throw new IOException("Stream is closed");
894
if (eof && nextout < 0) // eof and no leftover
895
return -1;
896
if (off < 0 || len < 0 || len > b.length - off)
897
throw new IndexOutOfBoundsException();
898
int oldOff = off;
899
if (nextout >= 0) { // leftover output byte(s) in bits buf
900
do {
901
if (len == 0)
902
return off - oldOff;
903
b[off++] = (byte)(bits >> nextout);
904
len--;
905
nextout -= 8;
906
} while (nextout >= 0);
907
bits = 0;
908
}
909
while (len > 0) {
910
int v = is.read();
911
if (v == -1) {
912
eof = true;
913
if (nextin != 18) {
914
if (nextin == 12)
915
throw new IOException("Base64 stream has one un-decoded dangling byte.");
916
// treat ending xx/xxx without padding character legal.
917
// same logic as v == '=' below
918
b[off++] = (byte)(bits >> (16));
919
len--;
920
if (nextin == 0) { // only one padding byte
921
if (len == 0) { // no enough output space
922
bits >>= 8; // shift to lowest byte
923
nextout = 0;
924
} else {
925
b[off++] = (byte) (bits >> 8);
926
}
927
}
928
}
929
if (off == oldOff)
930
return -1;
931
else
932
return off - oldOff;
933
}
934
if (v == '=') { // padding byte(s)
935
// = shiftto==18 unnecessary padding
936
// x= shiftto==12 dangling x, invalid unit
937
// xx= shiftto==6 && missing last '='
938
// xx=y or last is not '='
939
if (nextin == 18 || nextin == 12 ||
940
nextin == 6 && is.read() != '=') {
941
throw new IOException("Illegal base64 ending sequence:" + nextin);
942
}
943
b[off++] = (byte)(bits >> (16));
944
len--;
945
if (nextin == 0) { // only one padding byte
946
if (len == 0) { // no enough output space
947
bits >>= 8; // shift to lowest byte
948
nextout = 0;
949
} else {
950
b[off++] = (byte) (bits >> 8);
951
}
952
}
953
eof = true;
954
break;
955
}
956
if ((v = base64[v]) == -1) {
957
if (isMIME) // skip if for rfc2045
958
continue;
959
else
960
throw new IOException("Illegal base64 character " +
961
Integer.toString(v, 16));
962
}
963
bits |= (v << nextin);
964
if (nextin == 0) {
965
nextin = 18; // clear for next
966
nextout = 16;
967
while (nextout >= 0) {
968
b[off++] = (byte)(bits >> nextout);
969
len--;
970
nextout -= 8;
971
if (len == 0 && nextout >= 0) { // don't clean "bits"
972
return off - oldOff;
973
}
974
}
975
bits = 0;
976
} else {
977
nextin -= 6;
978
}
979
}
980
return off - oldOff;
981
}
982
983
@Override
984
public int available() throws IOException {
985
if (closed)
986
throw new IOException("Stream is closed");
987
return is.available(); // TBD:
988
}
989
990
@Override
991
public void close() throws IOException {
992
if (!closed) {
993
closed = true;
994
is.close();
995
}
996
}
997
}
998
}
999
1000