Skip to content

Commit 1ada05b

Browse files
committed
#923 Fixed: IndexOutOfBoundsException in FBCachedBlob.getBytes(long, int) for position or length beyond end of data
1 parent cf0a085 commit 1ada05b

2 files changed

Lines changed: 51 additions & 12 deletions

File tree

src/main/org/firebirdsql/jdbc/FBCachedBlob.java

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import org.firebirdsql.gds.JaybirdErrorCodes;
1010
import org.firebirdsql.gds.ng.FbExceptionBuilder;
11+
import org.firebirdsql.jaybird.util.ByteArrayHelper;
1112
import org.firebirdsql.util.InternalApi;
1213
import org.jspecify.annotations.NullMarked;
1314

@@ -47,8 +48,7 @@ public FBCachedBlob(byte[] data) {
4748

4849
@Override
4950
public FirebirdBlob detach() throws SQLException {
50-
checkOpen();
51-
return new FBCachedBlob(blobData);
51+
return new FBCachedBlob(requireBlobData());
5252
}
5353

5454
/**
@@ -70,32 +70,31 @@ public boolean isSegmented() throws SQLException {
7070
*/
7171
@Override
7272
public long length() throws SQLException {
73-
checkOpen();
74-
return blobData.length;
73+
return requireBlobData().length;
7574
}
7675

7776
@Override
7877
public byte[] getBytes(long pos, int length) throws SQLException {
7978
if (pos < 1) {
8079
throw new SQLException("Expected value of pos > 0, got " + pos,
8180
SQLStateConstants.SQL_STATE_INVALID_STRING_LENGTH);
82-
}
83-
if (length < 0) {
81+
} else if (length < 0) {
8482
throw new SQLException("Expected value of length >= 0, got " + length,
8583
SQLStateConstants.SQL_STATE_INVALID_STRING_LENGTH);
8684
}
87-
checkOpen();
85+
byte[] blobData = requireBlobData();
86+
87+
if (pos > blobData.length) return ByteArrayHelper.emptyByteArray();
88+
length = (int) Math.min(length, blobData.length - pos + 1L);
8889

89-
// TODO What if pos or length are beyond blobData
9090
byte[] result = new byte[length];
9191
System.arraycopy(blobData, (int) pos - 1, result, 0, length);
9292
return result;
9393
}
9494

9595
@Override
9696
public byte[] getBytes() throws SQLException {
97-
checkOpen();
98-
return blobData.clone();
97+
return requireBlobData().clone();
9998
}
10099

101100
/**
@@ -122,8 +121,7 @@ public long position(Blob pattern, long start) throws SQLException {
122121

123122
@Override
124123
public InputStream getBinaryStream() throws SQLException {
125-
checkOpen();
126-
return new ByteArrayInputStream(blobData);
124+
return new ByteArrayInputStream(requireBlobData());
127125
}
128126

129127
@Override
@@ -180,9 +178,25 @@ public void free() throws SQLException {
180178
blobData = FREED_MARKER;
181179
}
182180

181+
/**
182+
* Checks if the blob is open (not freed).
183+
* <p>
184+
* If a method needs access to the blob data, use {@link #requireBlobData()} instead.
185+
* </p>
186+
*
187+
* @see #requireBlobData()
188+
*/
183189
private void checkOpen() throws SQLException {
184190
if (blobData == FREED_MARKER) {
185191
throw FbExceptionBuilder.toException(JaybirdErrorCodes.jb_blobClosed);
186192
}
187193
}
194+
195+
private byte[] requireBlobData() throws SQLException {
196+
byte[] blobData = this.blobData;
197+
if (blobData == FREED_MARKER) {
198+
throw FbExceptionBuilder.toException(JaybirdErrorCodes.jb_blobClosed);
199+
}
200+
return blobData;
201+
}
188202
}

src/test/org/firebirdsql/jdbc/FBCachedBlobTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import org.firebirdsql.gds.JaybirdErrorCodes;
66
import org.hamcrest.Matcher;
77
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.params.ParameterizedTest;
9+
import org.junit.jupiter.params.provider.ValueSource;
810

911
import java.io.ByteArrayOutputStream;
1012
import java.io.InputStream;
@@ -15,6 +17,7 @@
1517
import static org.firebirdsql.common.matchers.SQLExceptionMatchers.*;
1618
import static org.hamcrest.MatcherAssert.assertThat;
1719
import static org.hamcrest.CoreMatchers.*;
20+
import static org.hamcrest.Matchers.greaterThan;
1821
import static org.junit.jupiter.api.Assertions.*;
1922

2023
/**
@@ -129,6 +132,28 @@ void testGetBytes_long_int() throws Exception {
129132
assertArrayEquals(new byte[] { 5, 6, 7, 8, 9 }, data, "Unexpected data");
130133
}
131134

135+
@ParameterizedTest
136+
@ValueSource(longs = { 11, Integer.MAX_VALUE, Integer.MAX_VALUE + 1L, Long.MAX_VALUE })
137+
void testGetBytes_long_int_posBeyondEnd(long pos) throws Exception {
138+
assertThat("Wrong value for pos", pos, greaterThan(10L));
139+
FBCachedBlob blob = new FBCachedBlob(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
140+
141+
byte[] data = blob.getBytes(pos, 5);
142+
143+
assertNotNull(data, "Expected non-null array");
144+
assertArrayEquals(new byte[0], data, "Unexpected data");
145+
}
146+
147+
@Test
148+
void testGetBytes_long_int_lengthBeyondEnd() throws Exception {
149+
FBCachedBlob blob = new FBCachedBlob(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
150+
151+
byte[] data = blob.getBytes(5, 10);
152+
153+
assertNotNull(data, "Expected non-null array");
154+
assertArrayEquals(new byte[] { 5, 6, 7, 8, 9, 10 }, data, "Unexpected data");
155+
}
156+
132157
@Test
133158
void testGetBytes() throws Exception {
134159
byte[] input = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

0 commit comments

Comments
 (0)