Skip to content

Commit 3792112

Browse files
committed
<action dev="ggregory" type="add" issue="CODEC-181" due-to="Ivan Martinez-Ortiz">Make possible to provide padding byte to BaseNCodec in constructor</action>
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/codec/trunk@1571406 13f79535-47bb-0310-9956-ffa450edef68
1 parent e199951 commit 3792112

6 files changed

Lines changed: 149 additions & 29 deletions

File tree

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ The <action> type attribute can be add,update,fix,remove.
4343
</properties>
4444
<body>
4545
<release version="1.10" date="DD Mmmm 2014" description="Feature and fix release.">
46+
<action dev="ggregory" type="add" issue="CODEC-181" due-to="Ivan Martinez-Ortiz">Make possible to provide padding byte to BaseNCodec in constructor</action>
4647
<action dev="ggregory" type="fix" issue="CODEC-180" due-to="Ville Skyttä">Fix Javadoc 1.8.0 errors</action>
4748
<action dev="ggregory" type="update" issue="CODEC-178">Deprecate Charsets Charset constants in favor of Java 7's java.nio.charset.StandardCharsets</action>
4849
</release>

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

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,17 @@ public Base32() {
159159
this(false);
160160
}
161161

162+
/**
163+
* Creates a Base32 codec used for decoding and encoding.
164+
* <p>
165+
* When encoding the line length is 0 (no chunking).
166+
* </p>
167+
* @param pad byte used as padding byte.
168+
*/
169+
public Base32(final byte pad) {
170+
this(false, pad);
171+
}
172+
162173
/**
163174
* Creates a Base32 codec used for decoding and encoding.
164175
* <p>
@@ -167,7 +178,19 @@ public Base32() {
167178
* @param useHex if {@code true} then use Base32 Hex alphabet
168179
*/
169180
public Base32(final boolean useHex) {
170-
this(0, null, useHex);
181+
this(0, null, useHex, PAD_DEFAULT);
182+
}
183+
184+
/**
185+
* Creates a Base32 codec used for decoding and encoding.
186+
* <p>
187+
* When encoding the line length is 0 (no chunking).
188+
* </p>
189+
* @param useHex if {@code true} then use Base32 Hex alphabet
190+
* @param pad byte used as padding byte.
191+
*/
192+
public Base32(final boolean useHex, final byte pad) {
193+
this(0, null, useHex, pad);
171194
}
172195

173196
/**
@@ -204,7 +227,7 @@ public Base32(final int lineLength) {
204227
* The provided lineSeparator included some Base32 characters. That's not going to work!
205228
*/
206229
public Base32(final int lineLength, final byte[] lineSeparator) {
207-
this(lineLength, lineSeparator, false);
230+
this(lineLength, lineSeparator, false, PAD_DEFAULT);
208231
}
209232

210233
/**
@@ -229,10 +252,35 @@ public Base32(final int lineLength, final byte[] lineSeparator) {
229252
* lineLength &gt; 0 and lineSeparator is null.
230253
*/
231254
public Base32(final int lineLength, final byte[] lineSeparator, final boolean useHex) {
232-
super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK,
233-
lineLength,
234-
lineSeparator == null ? 0 : lineSeparator.length);
235-
if (useHex){
255+
this(lineLength, lineSeparator, useHex, PAD_DEFAULT);
256+
}
257+
258+
/**
259+
* Creates a Base32 / Base32 Hex codec used for decoding and encoding.
260+
* <p>
261+
* When encoding the line length and line separator are given in the constructor.
262+
* </p>
263+
* <p>
264+
* Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data.
265+
* </p>
266+
*
267+
* @param lineLength
268+
* Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
269+
* 8). If lineLength &lt;= 0, then the output will not be divided into lines (chunks). Ignored when
270+
* decoding.
271+
* @param lineSeparator
272+
* Each line of encoded data will end with this sequence of bytes.
273+
* @param useHex
274+
* if {@code true}, then use Base32 Hex alphabet, otherwise use Base32 alphabet
275+
* @param pad byte used as padding byte.
276+
* @throws IllegalArgumentException
277+
* The provided lineSeparator included some Base32 characters. That's not going to work! Or the
278+
* lineLength &gt; 0 and lineSeparator is null.
279+
*/
280+
public Base32(final int lineLength, final byte[] lineSeparator, final boolean useHex, final byte pad) {
281+
super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, lineLength,
282+
lineSeparator == null ? 0 : lineSeparator.length, pad);
283+
if (useHex) {
236284
this.encodeTable = HEX_ENCODE_TABLE;
237285
this.decodeTable = HEX_DECODE_TABLE;
238286
} else {
@@ -241,7 +289,7 @@ public Base32(final int lineLength, final byte[] lineSeparator, final boolean us
241289
}
242290
if (lineLength > 0) {
243291
if (lineSeparator == null) {
244-
throw new IllegalArgumentException("lineLength "+lineLength+" > 0, but lineSeparator is null");
292+
throw new IllegalArgumentException("lineLength " + lineLength + " > 0, but lineSeparator is null");
245293
}
246294
// Must be done after initializing the tables
247295
if (containsAlphabetOrPad(lineSeparator)) {
@@ -256,6 +304,10 @@ public Base32(final int lineLength, final byte[] lineSeparator, final boolean us
256304
this.lineSeparator = null;
257305
}
258306
this.decodeSize = this.encodeSize - 1;
307+
308+
if (isInAlphabet(pad) || isWhiteSpace(pad)) {
309+
throw new IllegalArgumentException("pad must not be in alphabet or whitespace");
310+
}
259311
}
260312

261313
/**
@@ -292,7 +344,7 @@ void decode(final byte[] in, int inPos, final int inAvail, final Context context
292344
}
293345
for (int i = 0; i < inAvail; i++) {
294346
final byte b = in[inPos++];
295-
if (b == PAD) {
347+
if (b == pad) {
296348
// We're done.
297349
context.eof = true;
298350
break;
@@ -398,32 +450,32 @@ void encode(final byte[] in, int inPos, final int inAvail, final Context context
398450
case 1 : // Only 1 octet; take top 5 bits then remainder
399451
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 3) & MASK_5BITS]; // 8-1*5 = 3
400452
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 2) & MASK_5BITS]; // 5-3=2
401-
buffer[context.pos++] = PAD;
402-
buffer[context.pos++] = PAD;
403-
buffer[context.pos++] = PAD;
404-
buffer[context.pos++] = PAD;
405-
buffer[context.pos++] = PAD;
406-
buffer[context.pos++] = PAD;
453+
buffer[context.pos++] = pad;
454+
buffer[context.pos++] = pad;
455+
buffer[context.pos++] = pad;
456+
buffer[context.pos++] = pad;
457+
buffer[context.pos++] = pad;
458+
buffer[context.pos++] = pad;
407459
break;
408460
case 2 : // 2 octets = 16 bits to use
409461
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 11) & MASK_5BITS]; // 16-1*5 = 11
410462
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 6) & MASK_5BITS]; // 16-2*5 = 6
411463
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 1) & MASK_5BITS]; // 16-3*5 = 1
412464
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 4) & MASK_5BITS]; // 5-1 = 4
413-
buffer[context.pos++] = PAD;
414-
buffer[context.pos++] = PAD;
415-
buffer[context.pos++] = PAD;
416-
buffer[context.pos++] = PAD;
465+
buffer[context.pos++] = pad;
466+
buffer[context.pos++] = pad;
467+
buffer[context.pos++] = pad;
468+
buffer[context.pos++] = pad;
417469
break;
418470
case 3 : // 3 octets = 24 bits to use
419471
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 19) & MASK_5BITS]; // 24-1*5 = 19
420472
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 14) & MASK_5BITS]; // 24-2*5 = 14
421473
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 9) & MASK_5BITS]; // 24-3*5 = 9
422474
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 4) & MASK_5BITS]; // 24-4*5 = 4
423475
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 1) & MASK_5BITS]; // 5-4 = 1
424-
buffer[context.pos++] = PAD;
425-
buffer[context.pos++] = PAD;
426-
buffer[context.pos++] = PAD;
476+
buffer[context.pos++] = pad;
477+
buffer[context.pos++] = pad;
478+
buffer[context.pos++] = pad;
427479
break;
428480
case 4 : // 4 octets = 32 bits to use
429481
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 27) & MASK_5BITS]; // 32-1*5 = 27
@@ -433,7 +485,7 @@ void encode(final byte[] in, int inPos, final int inAvail, final Context context
433485
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 7) & MASK_5BITS]; // 32-5*5 = 7
434486
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 2) & MASK_5BITS]; // 32-6*5 = 2
435487
buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 3) & MASK_5BITS]; // 5-2 = 3
436-
buffer[context.pos++] = PAD;
488+
buffer[context.pos++] = pad;
437489
break;
438490
default:
439491
throw new IllegalStateException("Impossible modulus "+context.modulus);

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,8 @@ void encode(final byte[] in, int inPos, final int inAvail, final Context context
348348
buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS];
349349
// URL-SAFE skips the padding to further reduce size.
350350
if (encodeTable == STANDARD_ENCODE_TABLE) {
351-
buffer[context.pos++] = PAD;
352-
buffer[context.pos++] = PAD;
351+
buffer[context.pos++] = pad;
352+
buffer[context.pos++] = pad;
353353
}
354354
break;
355355

@@ -359,7 +359,7 @@ void encode(final byte[] in, int inPos, final int inAvail, final Context context
359359
buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS];
360360
// URL-SAFE skips the padding to further reduce size.
361361
if (encodeTable == STANDARD_ENCODE_TABLE) {
362-
buffer[context.pos++] = PAD;
362+
buffer[context.pos++] = pad;
363363
}
364364
break;
365365
default:
@@ -432,7 +432,7 @@ void decode(final byte[] in, int inPos, final int inAvail, final Context context
432432
for (int i = 0; i < inAvail; i++) {
433433
final byte[] buffer = ensureBufferSize(decodeSize, context);
434434
final byte b = in[inPos++];
435-
if (b == PAD) {
435+
if (b == pad) {
436436
// We're done.
437437
context.eof = true;
438438
break;

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,14 @@ public String toString() {
153153
*/
154154
protected static final byte PAD_DEFAULT = '='; // Allow static access to default
155155

156+
/**
157+
* @deprecated Use {@link #pad}. Will be removed in 2.0.
158+
*/
159+
@Deprecated
156160
protected final byte PAD = PAD_DEFAULT; // instance variable just in case it needs to vary later
157161

162+
protected final byte pad; // instance variable just in case it needs to vary later
163+
158164
/** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */
159165
private final int unencodedBlockSize;
160166

@@ -183,11 +189,27 @@ public String toString() {
183189
*/
184190
protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize,
185191
final int lineLength, final int chunkSeparatorLength) {
192+
this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, PAD_DEFAULT);
193+
}
194+
195+
/**
196+
* Note <code>lineLength</code> is rounded down to the nearest multiple of {@link #encodedBlockSize}
197+
* If <code>chunkSeparatorLength</code> is zero, then chunking is disabled.
198+
* @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3)
199+
* @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4)
200+
* @param lineLength if &gt; 0, use chunking with a length <code>lineLength</code>
201+
* @param chunkSeparatorLength the chunk separator length, if relevant
202+
* @param pad byte used as padding byte.
203+
*/
204+
protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize,
205+
final int lineLength, final int chunkSeparatorLength, final byte pad) {
186206
this.unencodedBlockSize = unencodedBlockSize;
187207
this.encodedBlockSize = encodedBlockSize;
188208
final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0;
189209
this.lineLength = useChunking ? (lineLength / encodedBlockSize) * encodedBlockSize : 0;
190210
this.chunkSeparatorLength = chunkSeparatorLength;
211+
212+
this.pad = pad;
191213
}
192214

193215
/**
@@ -440,7 +462,7 @@ public byte[] encode(final byte[] pArray) {
440462
public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) {
441463
for (int i = 0; i < arrayOctet.length; i++) {
442464
if (!isInAlphabet(arrayOctet[i]) &&
443-
(!allowWSPad || (arrayOctet[i] != PAD) && !isWhiteSpace(arrayOctet[i]))) {
465+
(!allowWSPad || (arrayOctet[i] != pad) && !isWhiteSpace(arrayOctet[i]))) {
444466
return false;
445467
}
446468
}
@@ -474,7 +496,7 @@ protected boolean containsAlphabetOrPad(final byte[] arrayOctet) {
474496
return false;
475497
}
476498
for (final byte element : arrayOctet) {
477-
if (PAD == element || isInAlphabet(element)) {
499+
if (pad == element || isInAlphabet(element)) {
478500
return true;
479501
}
480502
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ public class Base32Test {
5959
{"foobar" ,"MZXW6YTBOI======\r\n"},
6060
};
6161

62+
private static final String [][] BASE32_PAD_TEST_CASES = { // RFC 4648
63+
{"" ,""},
64+
{"f" ,"MY%%%%%%"},
65+
{"fo" ,"MZXQ%%%%"},
66+
{"foo" ,"MZXW6%%%"},
67+
{"foob" ,"MZXW6YQ%"},
68+
{"fooba" ,"MZXW6YTB"},
69+
{"foobar" ,"MZXW6YTBOI%%%%%%"},
70+
};
71+
6272
@Test
6373
public void testBase32Samples() throws Exception {
6474
final Base32 codec = new Base32();
@@ -132,4 +142,13 @@ public void testRandomBytesHex() {
132142
//assertEquals(b[0],codec.decode(b[1]));
133143
}
134144
}
145+
146+
@Test
147+
public void testBase32SamplesNonDefaultPadding() throws Exception {
148+
final Base32 codec = new Base32((byte)0x25); // '%' <=> 0x25
149+
150+
for (final String[] element : BASE32_PAD_TEST_CASES) {
151+
assertEquals(element[1], codec.encodeAsString(element[0].getBytes(Charsets.UTF_8)));
152+
}
153+
}
135154
}

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static org.junit.Assert.assertFalse;
2222
import static org.junit.Assert.assertNotNull;
2323
import static org.junit.Assert.assertTrue;
24+
import static org.junit.Assert.assertEquals;
2425

2526
import org.junit.Before;
2627
import org.junit.Test;
@@ -158,11 +159,36 @@ public void testContainsAlphabetOrPad() {
158159
assertTrue(codec.containsAlphabetOrPad("OK".getBytes()));
159160
assertTrue(codec.containsAlphabetOrPad("OK ".getBytes()));
160161
assertFalse(codec.containsAlphabetOrPad("ok ".getBytes()));
161-
assertTrue(codec.containsAlphabetOrPad(new byte[]{codec.PAD}));
162+
assertTrue(codec.containsAlphabetOrPad(new byte[]{codec.pad}));
162163
}
163164

164165
// @Test
165166
// public void testGetEncodedLength() {
166167
// fail("Not yet implemented");
167168
// }
169+
170+
@Test
171+
public void testProvidePaddingByte() {
172+
// Given
173+
codec = new BaseNCodec(0, 0, 0, 0, (byte)0x25) {
174+
@Override
175+
protected boolean isInAlphabet(final byte b) {
176+
return b=='O' || b == 'K'; // allow OK
177+
}
178+
179+
@Override
180+
void encode(final byte[] pArray, final int i, final int length, final Context context) {
181+
}
182+
183+
@Override
184+
void decode(final byte[] pArray, final int i, final int length, final Context context) {
185+
}
186+
};
187+
188+
// When
189+
byte actualPaddingByte = codec.pad;
190+
191+
// Then
192+
assertEquals(0x25, actualPaddingByte);
193+
}
168194
}

0 commit comments

Comments
 (0)