Skip to content

Commit a6b2f13

Browse files
committed
CODEC-289: Add strict decoding to BaseNCodecInput/OutputStream
1 parent dce9e54 commit a6b2f13

9 files changed

Lines changed: 201 additions & 2 deletions

File tree

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ The <action> type attribute can be add,update,fix,remove.
4545
<release version="1.15" date="YYYY-MM-DD" description="Feature and fix release.">
4646
<action issue="CODEC-264" dev="aherbert" due-to="Andy Seaborne" type="fix">MurmurHash3: Ensure hash128 maintains the sign extension bug.</action>
4747
<action issue="CODEC-280" dev="aherbert" type="update">Base32/Base64/BCodec: Added strict decoding property to control handling of trailing bits. Default lenient mode discards them without error. Strict mode raise an exception.</action>
48+
<action issue="CODEC-289" dev="aherbert" type="update">Base32/Base64 Input/OutputStream: Added strict decoding property to control handling of trailing bits. Default lenient mode discards them without error. Strict mode raise an exception.</action>
4849
</release>
4950

5051
<release version="1.14" date="2019-12-30" description="Feature and fix release.">

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,40 @@ protected BaseNCodecInputStream(final InputStream input, final BaseNCodec baseNC
4747
this.baseNCodec = baseNCodec;
4848
}
4949

50+
/**
51+
* Sets the decoding behavior when the input bytes contain leftover trailing bits that
52+
* cannot be created by a valid encoding. This setting is transferred to the instance
53+
* of {@link BaseNCodec} used to perform decoding.
54+
*
55+
* <p>The default is false for lenient encoding. Decoding will compose trailing bits
56+
* into 8-bit bytes and discard the remainder.
57+
*
58+
* <p>Set to true to enable strict decoding. Decoding will raise an
59+
* {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
60+
*
61+
* @param strictDecoding Set to true to enable strict decoding; otherwise use lenient decoding.
62+
* @see BaseNCodec#setStrictDecoding(boolean)
63+
* @since 1.15
64+
*/
65+
public void setStrictDecoding(boolean strictDecoding) {
66+
baseNCodec.setStrictDecoding(strictDecoding);
67+
}
68+
69+
/**
70+
* Returns true if decoding behavior is strict. Decoding will raise an
71+
* {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
72+
*
73+
* <p>The default is false for lenient encoding. Decoding will compose trailing bits
74+
* into 8-bit bytes and discard the remainder.
75+
*
76+
* @return true if using strict decoding
77+
* @see #setStrictDecoding(boolean)
78+
* @since 1.15
79+
*/
80+
public boolean isStrictDecoding() {
81+
return baseNCodec.isStrictDecoding();
82+
}
83+
5084
/**
5185
* {@inheritDoc}
5286
*

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,40 @@ public BaseNCodecOutputStream(final OutputStream output, final BaseNCodec basedC
6060
this.doEncode = doEncode;
6161
}
6262

63+
/**
64+
* Sets the decoding behavior when the input bytes contain leftover trailing bits that
65+
* cannot be created by a valid encoding. This setting is transferred to the instance
66+
* of {@link BaseNCodec} used to perform decoding.
67+
*
68+
* <p>The default is false for lenient encoding. Decoding will compose trailing bits
69+
* into 8-bit bytes and discard the remainder.
70+
*
71+
* <p>Set to true to enable strict decoding. Decoding will raise an
72+
* {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
73+
*
74+
* @param strictDecoding Set to true to enable strict decoding; otherwise use lenient decoding.
75+
* @see BaseNCodec#setStrictDecoding(boolean)
76+
* @since 1.15
77+
*/
78+
public void setStrictDecoding(boolean strictDecoding) {
79+
baseNCodec.setStrictDecoding(strictDecoding);
80+
}
81+
82+
/**
83+
* Returns true if decoding behavior is strict. Decoding will raise an
84+
* {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
85+
*
86+
* <p>The default is false for lenient encoding. Decoding will compose trailing bits
87+
* into 8-bit bytes and discard the remainder.
88+
*
89+
* @return true if using strict decoding
90+
* @see #setStrictDecoding(boolean)
91+
* @since 1.15
92+
*/
93+
public boolean isStrictDecoding() {
94+
return baseNCodec.isStrictDecoding();
95+
}
96+
6397
/**
6498
* Writes the specified {@code byte} to this output stream.
6599
*

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,4 +557,32 @@ public void testSkipWrongArgument() throws Throwable {
557557
b32stream.skip(-10);
558558
}
559559
}
560+
561+
/**
562+
* Test strict decoding.
563+
*
564+
* @throws Exception
565+
* for some failure scenarios.
566+
*/
567+
@Test
568+
public void testStrictDecoding() throws Exception {
569+
for (final String s : Base32Test.BASE32_IMPOSSIBLE_CASES) {
570+
final byte[] encoded = StringUtils.getBytesUtf8(s);
571+
Base32InputStream in = new Base32InputStream(new ByteArrayInputStream(encoded), false);
572+
// Default is lenient decoding; it should not throw
573+
assertFalse(in.isStrictDecoding());
574+
Base32TestData.streamToBytes(in);
575+
576+
// Strict decoding should throw
577+
in = new Base32InputStream(new ByteArrayInputStream(encoded), false);
578+
in.setStrictDecoding(true);
579+
assertTrue(in.isStrictDecoding());
580+
try {
581+
Base32TestData.streamToBytes(in);
582+
fail();
583+
} catch (final IllegalArgumentException ex) {
584+
// expected
585+
}
586+
}
587+
}
560588
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package org.apache.commons.codec.binary;
1919

20+
import static org.junit.Assert.assertFalse;
2021
import static org.junit.Assert.assertTrue;
2122
import static org.junit.Assert.fail;
2223

@@ -333,4 +334,36 @@ public void testWriteToNullCoverage() throws Exception {
333334
}
334335
}
335336

337+
/**
338+
* Test strict decoding.
339+
*
340+
* @throws Exception
341+
* for some failure scenarios.
342+
*/
343+
@Test
344+
public void testStrictDecoding() throws Exception {
345+
for (final String s : Base32Test.BASE32_IMPOSSIBLE_CASES) {
346+
final byte[] encoded = StringUtils.getBytesUtf8(s);
347+
ByteArrayOutputStream bout = new ByteArrayOutputStream();
348+
Base32OutputStream out = new Base32OutputStream(bout, false);
349+
// Default is lenient decoding; it should not throw
350+
assertFalse(out.isStrictDecoding());
351+
out.write(encoded);
352+
out.close();
353+
assertTrue(bout.size() > 0);
354+
355+
// Strict decoding should throw
356+
bout = new ByteArrayOutputStream();
357+
out = new Base32OutputStream(bout, false);
358+
out.setStrictDecoding(true);
359+
assertTrue(out.isStrictDecoding());
360+
try {
361+
out.write(encoded);
362+
out.close();
363+
fail();
364+
} catch (final IllegalArgumentException ex) {
365+
// expected
366+
}
367+
}
368+
}
336369
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ public class Base32Test {
4848
{"foobar" ,"MZXW6YTBOI======"},
4949
};
5050

51-
private static final String[] BASE32_IMPOSSIBLE_CASES = {
51+
/**
52+
* Example test cases with valid characters but impossible combinations of
53+
* trailing characters (i.e. cannot be created during encoding).
54+
*/
55+
static final String[] BASE32_IMPOSSIBLE_CASES = {
5256
"MC======",
5357
"MZXE====",
5458
"MZXWB===",

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,4 +570,32 @@ public void testSkipWrongArgument() throws Throwable {
570570
b64stream.skip(-10);
571571
}
572572
}
573+
574+
/**
575+
* Test strict decoding.
576+
*
577+
* @throws Exception
578+
* for some failure scenarios.
579+
*/
580+
@Test
581+
public void testStrictDecoding() throws Exception {
582+
for (final String s : Base64Test.BASE64_IMPOSSIBLE_CASES) {
583+
final byte[] encoded = StringUtils.getBytesUtf8(s);
584+
Base64InputStream in = new Base64InputStream(new ByteArrayInputStream(encoded), false);
585+
// Default is lenient decoding; it should not throw
586+
assertFalse(in.isStrictDecoding());
587+
Base64TestData.streamToBytes(in);
588+
589+
// Strict decoding should throw
590+
in = new Base64InputStream(new ByteArrayInputStream(encoded), false);
591+
in.setStrictDecoding(true);
592+
assertTrue(in.isStrictDecoding());
593+
try {
594+
Base64TestData.streamToBytes(in);
595+
fail();
596+
} catch (final IllegalArgumentException ex) {
597+
// expected
598+
}
599+
}
600+
}
573601
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.commons.codec.binary;
1919

2020
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertFalse;
2122
import static org.junit.Assert.assertTrue;
2223
import static org.junit.Assert.fail;
2324

@@ -341,4 +342,36 @@ public void testWriteToNullCoverage() throws Exception {
341342
}
342343
}
343344

345+
/**
346+
* Test strict decoding.
347+
*
348+
* @throws Exception
349+
* for some failure scenarios.
350+
*/
351+
@Test
352+
public void testStrictDecoding() throws Exception {
353+
for (final String s : Base64Test.BASE64_IMPOSSIBLE_CASES) {
354+
final byte[] encoded = StringUtils.getBytesUtf8(s);
355+
ByteArrayOutputStream bout = new ByteArrayOutputStream();
356+
Base64OutputStream out = new Base64OutputStream(bout, false);
357+
// Default is lenient decoding; it should not throw
358+
assertFalse(out.isStrictDecoding());
359+
out.write(encoded);
360+
out.close();
361+
assertTrue(bout.size() > 0);
362+
363+
// Strict decoding should throw
364+
bout = new ByteArrayOutputStream();
365+
out = new Base64OutputStream(bout, false);
366+
out.setStrictDecoding(true);
367+
assertTrue(out.isStrictDecoding());
368+
try {
369+
out.write(encoded);
370+
out.close();
371+
fail();
372+
} catch (final IllegalArgumentException ex) {
373+
// expected
374+
}
375+
}
376+
}
344377
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ public class Base64Test {
4545

4646
private static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8;
4747

48-
private static final String[] BASE64_IMPOSSIBLE_CASES = {
48+
/**
49+
* Example test cases with valid characters but impossible combinations of
50+
* trailing characters (i.e. cannot be created during encoding).
51+
*/
52+
static final String[] BASE64_IMPOSSIBLE_CASES = {
4953
"ZE==",
5054
"ZmC=",
5155
"Zm9vYE==",

0 commit comments

Comments
 (0)