Skip to content

Commit 2c13032

Browse files
committed
[CODEC-105] ArrayIndexOutOfBoundsException when doing multiple reads() on encoding b64InputStream. https://issues.apache.org/jira/browse/CODEC-105
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/codec/trunk@1062592 13f79535-47bb-0310-9956-ffa450edef68
1 parent 931e259 commit 2c13032

4 files changed

Lines changed: 74 additions & 61 deletions

File tree

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

Lines changed: 10 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -406,43 +406,16 @@ private void resizeBuffer() {
406406
int readResults(byte[] b, int bPos, int bAvail) {
407407
if (buffer != null) {
408408
int len = Math.min(avail(), bAvail);
409-
if (buffer != b) {
410-
System.arraycopy(buffer, readPos, b, bPos, len);
411-
readPos += len;
412-
if (readPos >= pos) {
413-
buffer = null;
414-
}
415-
} else {
416-
// Re-using the original consumer's output array is only
417-
// allowed for one round.
409+
System.arraycopy(buffer, readPos, b, bPos, len);
410+
readPos += len;
411+
if (readPos >= pos) {
418412
buffer = null;
419413
}
420414
return len;
421415
}
422416
return eof ? -1 : 0;
423417
}
424418

425-
/**
426-
* Sets the streaming buffer. This is a small optimization where we try to buffer directly to the consumer's output
427-
* array for one round (if the consumer calls this method first) instead of starting our own buffer.
428-
*
429-
* @param out
430-
* byte[] array to buffer directly to.
431-
* @param outPos
432-
* Position to start buffering into.
433-
* @param outAvail
434-
* Amount of bytes available for direct buffering.
435-
*/
436-
void setInitialBuffer(byte[] out, int outPos, int outAvail) {
437-
// We can re-use consumer's original output array under
438-
// special circumstances, saving on some System.arraycopy().
439-
if (out != null && out.length == outAvail) {
440-
buffer = out;
441-
pos = outPos;
442-
readPos = outPos;
443-
}
444-
}
445-
446419
/**
447420
* <p>
448421
* Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with
@@ -493,7 +466,10 @@ void encode(byte[] in, int inPos, int inAvail) {
493466
}
494467
break;
495468
}
496-
if (lineLength > 0 && pos > 0) {
469+
// Don't want to append the CRLF two times in a row, so make sure previous
470+
// character is not from CRLF!
471+
byte b = lineSeparator[lineSeparator.length - 1];
472+
if (lineLength > 0 && pos > 0 && buffer[pos-1] != b) {
497473
System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length);
498474
pos += lineSeparator.length;
499475
}
@@ -751,18 +727,8 @@ public byte[] decode(byte[] pArray) {
751727
if (pArray == null || pArray.length == 0) {
752728
return pArray;
753729
}
754-
long len = (pArray.length * 3) / 4;
755-
byte[] buf = new byte[(int) len];
756-
setInitialBuffer(buf, 0, buf.length);
757730
decode(pArray, 0, pArray.length);
758731
decode(pArray, 0, -1); // Notify decoder of EOF.
759-
760-
// Would be nice to just return buf (like we sometimes do in the encode
761-
// logic), but we have no idea what the line-length was (could even be
762-
// variable). So we cannot determine ahead of time exactly how big an
763-
// array is necessary. Hence the need to construct a 2nd byte array to
764-
// hold the final result:
765-
766732
byte[] result = new byte[pos];
767733
readResults(result, 0, result.length);
768734
return result;
@@ -946,23 +912,11 @@ public byte[] encode(byte[] pArray) {
946912
if (pArray == null || pArray.length == 0) {
947913
return pArray;
948914
}
949-
long len = getEncodeLength(pArray, lineLength, lineSeparator);
950-
byte[] buf = new byte[(int) len];
951-
setInitialBuffer(buf, 0, buf.length);
952915
encode(pArray, 0, pArray.length);
953916
encode(pArray, 0, -1); // Notify encoder of EOF.
954-
// Encoder might have resized, even though it was unnecessary.
955-
if (buffer != buf) {
956-
readResults(buf, 0, buf.length);
957-
}
958-
// In URL-SAFE mode we skip the padding characters, so sometimes our
959-
// final length is a bit smaller.
960-
if (isUrlSafe() && pos < buf.length) {
961-
byte[] smallerBuf = new byte[pos];
962-
System.arraycopy(buf, 0, smallerBuf, 0, pos);
963-
buf = smallerBuf;
964-
}
965-
return buf;
917+
byte[] buf = new byte[pos - readPos];
918+
readResults(buf, 0, buf.length);
919+
return buf;
966920
}
967921

968922
/**

src/java/org/apache/commons/codec/binary/Base64InputStream.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,6 @@ public int read(byte b[], int offset, int len) throws IOException {
166166
if (!base64.hasData()) {
167167
byte[] buf = new byte[doEncode ? 4096 : 8192];
168168
int c = in.read(buf);
169-
// A little optimization to avoid System.arraycopy()
170-
// when possible.
171-
if (c > 0 && b.length == len) {
172-
base64.setInitialBuffer(b, offset, len);
173-
}
174169
if (doEncode) {
175170
base64.encode(buf, 0, c);
176171
} else {

src/test/org/apache/commons/codec/binary/Base64InputStreamTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.BufferedReader;
2121
import java.io.ByteArrayInputStream;
22+
import java.io.IOException;
2223
import java.io.InputStream;
2324
import java.io.InputStreamReader;
2425
import java.util.Arrays;
@@ -48,6 +49,16 @@ public Base64InputStreamTest(String name) {
4849
super(name);
4950
}
5051

52+
/**
53+
* Tests the bug reported in CODEC-105. Bad interactions with InputStream when reading one byte at a time.
54+
*/
55+
public void testCodec105() throws IOException {
56+
Base64InputStream in = new Base64InputStream(new Codec105ErrorInputStream(), true, 0, null);
57+
for (int i = 0; i < 5; i++) {
58+
in.read();
59+
}
60+
}
61+
5162
/**
5263
* Test for the CODEC-101 bug: InputStream.read(byte[]) should never return 0
5364
* because Java's builtin InputStreamReader hates that.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.commons.codec.binary;
19+
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
23+
/**
24+
* Emits three line-feeds '\n' in a row, one at a time, and then EOF.
25+
*
26+
* Recreates the bug described in CODEC-105.
27+
*
28+
* @author Apache Software Foundation
29+
* @version $Id $
30+
* @since 1.5
31+
*/
32+
public class Codec105ErrorInputStream extends InputStream {
33+
private static final int EOF = -1;
34+
35+
int countdown = 3;
36+
37+
public int read() throws IOException {
38+
if (this.countdown-- > 0) {
39+
return '\n';
40+
} else {
41+
return EOF;
42+
}
43+
}
44+
45+
public int read(byte b[], int pos, int len) throws IOException {
46+
if (this.countdown-- > 0) {
47+
b[pos] = '\n';
48+
return 1;
49+
} else {
50+
return EOF;
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)