Skip to content

Commit 3f41cf1

Browse files
committed
[CODEC-96] Base64 encode() method is no longer thread-safe, breaking clients using it as a shared BinaryEncoder
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/codec/trunk@1309055 13f79535-47bb-0310-9956-ffa450edef68
1 parent f424976 commit 3f41cf1

7 files changed

Lines changed: 279 additions & 255 deletions

File tree

src/main/java/org/apache/commons/codec/binary/Base32.java

Lines changed: 97 additions & 97 deletions
Large diffs are not rendered by default.

src/main/java/org/apache/commons/codec/binary/Base64.java

Lines changed: 54 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
* character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc).
4141
* </p>
4242
* <p>
43-
* This class is not thread-safe. Each thread should use its own instance.
43+
* This class is thread-safe.
4444
* </p>
4545
*
4646
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
@@ -155,12 +155,6 @@ public class Base64 extends BaseNCodec {
155155
*/
156156
private final int encodeSize;
157157

158-
/**
159-
* Place holder for the bytes we're dealing with for our based logic.
160-
* Bitwise operations store and extract the encoding or decoding from this variable.
161-
*/
162-
private int bitWorkArea;
163-
164158
/**
165159
* Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
166160
* <p>
@@ -321,67 +315,68 @@ public boolean isUrlSafe() {
321315
* Position to start reading data from.
322316
* @param inAvail
323317
* Amount of bytes available from input for encoding.
318+
* @param context the context to be used
324319
*/
325320
@Override
326-
void encode(byte[] in, int inPos, int inAvail) {
327-
if (eof) {
321+
void encode(byte[] in, int inPos, int inAvail, Context context) {
322+
if (context.eof) {
328323
return;
329324
}
330325
// inAvail < 0 is how we're informed of EOF in the underlying data we're
331326
// encoding.
332327
if (inAvail < 0) {
333-
eof = true;
334-
if (0 == modulus && lineLength == 0) {
328+
context.eof = true;
329+
if (0 == context.modulus && lineLength == 0) {
335330
return; // no leftovers to process and not using chunking
336331
}
337-
ensureBufferSize(encodeSize);
338-
int savedPos = pos;
339-
switch (modulus) { // 0-2
332+
ensureBufferSize(encodeSize, context);
333+
int savedPos = context.pos;
334+
switch (context.modulus) { // 0-2
340335
case 1 : // 8 bits = 6 + 2
341-
buffer[pos++] = encodeTable[(bitWorkArea >> 2) & MASK_6BITS]; // top 6 bits
342-
buffer[pos++] = encodeTable[(bitWorkArea << 4) & MASK_6BITS]; // remaining 2
336+
context.buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2) & MASK_6BITS]; // top 6 bits
337+
context.buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS]; // remaining 2
343338
// URL-SAFE skips the padding to further reduce size.
344339
if (encodeTable == STANDARD_ENCODE_TABLE) {
345-
buffer[pos++] = PAD;
346-
buffer[pos++] = PAD;
340+
context.buffer[context.pos++] = PAD;
341+
context.buffer[context.pos++] = PAD;
347342
}
348343
break;
349344

350345
case 2 : // 16 bits = 6 + 6 + 4
351-
buffer[pos++] = encodeTable[(bitWorkArea >> 10) & MASK_6BITS];
352-
buffer[pos++] = encodeTable[(bitWorkArea >> 4) & MASK_6BITS];
353-
buffer[pos++] = encodeTable[(bitWorkArea << 2) & MASK_6BITS];
346+
context.buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10) & MASK_6BITS];
347+
context.buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4) & MASK_6BITS];
348+
context.buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS];
354349
// URL-SAFE skips the padding to further reduce size.
355350
if (encodeTable == STANDARD_ENCODE_TABLE) {
356-
buffer[pos++] = PAD;
351+
context.buffer[context.pos++] = PAD;
357352
}
358353
break;
359354
}
360-
currentLinePos += pos - savedPos; // keep track of current line position
355+
context.currentLinePos += context.pos - savedPos; // keep track of current line position
361356
// if currentPos == 0 we are at the start of a line, so don't add CRLF
362-
if (lineLength > 0 && currentLinePos > 0) {
363-
System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length);
364-
pos += lineSeparator.length;
357+
if (lineLength > 0 && context.currentLinePos > 0) {
358+
System.arraycopy(lineSeparator, 0, context.buffer, context.pos, lineSeparator.length);
359+
context.pos += lineSeparator.length;
365360
}
366361
} else {
367362
for (int i = 0; i < inAvail; i++) {
368-
ensureBufferSize(encodeSize);
369-
modulus = (modulus+1) % BYTES_PER_UNENCODED_BLOCK;
363+
ensureBufferSize(encodeSize, context);
364+
context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK;
370365
int b = in[inPos++];
371366
if (b < 0) {
372367
b += 256;
373368
}
374-
bitWorkArea = (bitWorkArea << 8) + b; // BITS_PER_BYTE
375-
if (0 == modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract
376-
buffer[pos++] = encodeTable[(bitWorkArea >> 18) & MASK_6BITS];
377-
buffer[pos++] = encodeTable[(bitWorkArea >> 12) & MASK_6BITS];
378-
buffer[pos++] = encodeTable[(bitWorkArea >> 6) & MASK_6BITS];
379-
buffer[pos++] = encodeTable[bitWorkArea & MASK_6BITS];
380-
currentLinePos += BYTES_PER_ENCODED_BLOCK;
381-
if (lineLength > 0 && lineLength <= currentLinePos) {
382-
System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length);
383-
pos += lineSeparator.length;
384-
currentLinePos = 0;
369+
context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE
370+
if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract
371+
context.buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18) & MASK_6BITS];
372+
context.buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12) & MASK_6BITS];
373+
context.buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6) & MASK_6BITS];
374+
context.buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS];
375+
context.currentLinePos += BYTES_PER_ENCODED_BLOCK;
376+
if (lineLength > 0 && lineLength <= context.currentLinePos) {
377+
System.arraycopy(lineSeparator, 0, context.buffer, context.pos, lineSeparator.length);
378+
context.pos += lineSeparator.length;
379+
context.currentLinePos = 0;
385380
}
386381
}
387382
}
@@ -410,32 +405,33 @@ void encode(byte[] in, int inPos, int inAvail) {
410405
* Position to start reading data from.
411406
* @param inAvail
412407
* Amount of bytes available from input for encoding.
408+
* @param context the context to be used
413409
*/
414410
@Override
415-
void decode(byte[] in, int inPos, int inAvail) {
416-
if (eof) {
411+
void decode(byte[] in, int inPos, int inAvail, Context context) {
412+
if (context.eof) {
417413
return;
418414
}
419415
if (inAvail < 0) {
420-
eof = true;
416+
context.eof = true;
421417
}
422418
for (int i = 0; i < inAvail; i++) {
423-
ensureBufferSize(decodeSize);
419+
ensureBufferSize(decodeSize, context);
424420
byte b = in[inPos++];
425421
if (b == PAD) {
426422
// We're done.
427-
eof = true;
423+
context.eof = true;
428424
break;
429425
} else {
430426
if (b >= 0 && b < DECODE_TABLE.length) {
431427
int result = DECODE_TABLE[b];
432428
if (result >= 0) {
433-
modulus = (modulus+1) % BYTES_PER_ENCODED_BLOCK;
434-
bitWorkArea = (bitWorkArea << BITS_PER_ENCODED_BYTE) + result;
435-
if (modulus == 0) {
436-
buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
437-
buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
438-
buffer[pos++] = (byte) (bitWorkArea & MASK_8BITS);
429+
context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK;
430+
context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result;
431+
if (context.modulus == 0) {
432+
context.buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS);
433+
context.buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
434+
context.buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS);
439435
}
440436
}
441437
}
@@ -445,22 +441,22 @@ void decode(byte[] in, int inPos, int inAvail) {
445441
// Two forms of EOF as far as base64 decoder is concerned: actual
446442
// EOF (-1) and first time '=' character is encountered in stream.
447443
// This approach makes the '=' padding characters completely optional.
448-
if (eof && modulus != 0) {
449-
ensureBufferSize(decodeSize);
444+
if (context.eof && context.modulus != 0) {
445+
ensureBufferSize(decodeSize, context);
450446

451447
// We have some spare bits remaining
452448
// Output all whole multiples of 8 bits and ignore the rest
453-
switch (modulus) {
449+
switch (context.modulus) {
454450
// case 1: // 6 bits - ignore entirely
455451
// break;
456452
case 2 : // 12 bits = 8 + 4
457-
bitWorkArea = bitWorkArea >> 4; // dump the extra 4 bits
458-
buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
453+
context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits
454+
context.buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
459455
break;
460456
case 3 : // 18 bits = 8 + 8 + 2
461-
bitWorkArea = bitWorkArea >> 2; // dump 2 bits
462-
buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
463-
buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
457+
context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits
458+
context.buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
459+
context.buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
464460
break;
465461
}
466462
}

0 commit comments

Comments
 (0)