提交 d5eb9e71 编写于 作者: S sherman

8026330: java.util.Base64 urlEncoder should omit padding

Summary: to add Encoder.withoutPadding()
Reviewed-by: alanb
上级 01f0a918
...@@ -138,7 +138,7 @@ public class Base64 { ...@@ -138,7 +138,7 @@ public class Base64 {
if (lineLength <= 0) { if (lineLength <= 0) {
return Encoder.RFC4648; return Encoder.RFC4648;
} }
return new Encoder(false, lineSeparator, lineLength >> 2 << 2); return new Encoder(false, lineSeparator, lineLength >> 2 << 2, true);
} }
/** /**
...@@ -192,11 +192,13 @@ public class Base64 { ...@@ -192,11 +192,13 @@ public class Base64 {
private final byte[] newline; private final byte[] newline;
private final int linemax; private final int linemax;
private final boolean isURL; private final boolean isURL;
private final boolean doPadding;
private Encoder(boolean isURL, byte[] newline, int linemax) { private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) {
this.isURL = isURL; this.isURL = isURL;
this.newline = newline; this.newline = newline;
this.linemax = linemax; this.linemax = linemax;
this.doPadding = doPadding;
} }
/** /**
...@@ -228,9 +230,22 @@ public class Base64 { ...@@ -228,9 +230,22 @@ public class Base64 {
private static final int MIMELINEMAX = 76; private static final int MIMELINEMAX = 76;
private static final byte[] CRLF = new byte[] {'\r', '\n'}; private static final byte[] CRLF = new byte[] {'\r', '\n'};
static final Encoder RFC4648 = new Encoder(false, null, -1); static final Encoder RFC4648 = new Encoder(false, null, -1, true);
static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1); static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true);
static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX); static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true);
private final int outLength(int srclen) {
int len = 0;
if (doPadding) {
len = 4 * ((srclen + 2) / 3);
} else {
int n = srclen % 3;
len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1);
}
if (linemax > 0) // line separators
len += (len - 1) / linemax * newline.length;
return len;
}
/** /**
* Encodes all bytes from the specified byte array into a newly-allocated * Encodes all bytes from the specified byte array into a newly-allocated
...@@ -243,9 +258,7 @@ public class Base64 { ...@@ -243,9 +258,7 @@ public class Base64 {
* encoded bytes. * encoded bytes.
*/ */
public byte[] encode(byte[] src) { public byte[] encode(byte[] src) {
int len = 4 * ((src.length + 2) / 3); // dst array size int len = outLength(src.length); // dst array size
if (linemax > 0) // line separators
len += (len - 1) / linemax * newline.length;
byte[] dst = new byte[len]; byte[] dst = new byte[len];
int ret = encode0(src, 0, src.length, dst); int ret = encode0(src, 0, src.length, dst);
if (ret != dst.length) if (ret != dst.length)
...@@ -273,10 +286,7 @@ public class Base64 { ...@@ -273,10 +286,7 @@ public class Base64 {
* space for encoding all input bytes. * space for encoding all input bytes.
*/ */
public int encode(byte[] src, byte[] dst) { public int encode(byte[] src, byte[] dst) {
int len = 4 * ((src.length + 2) / 3); // dst array size int len = outLength(src.length); // dst array size
if (linemax > 0) {
len += (len - 1) / linemax * newline.length;
}
if (dst.length < len) if (dst.length < len)
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Output byte array is too small for encoding all input bytes"); "Output byte array is too small for encoding all input bytes");
...@@ -321,9 +331,7 @@ public class Base64 { ...@@ -321,9 +331,7 @@ public class Base64 {
* @return A newly-allocated byte buffer containing the encoded bytes. * @return A newly-allocated byte buffer containing the encoded bytes.
*/ */
public ByteBuffer encode(ByteBuffer buffer) { public ByteBuffer encode(ByteBuffer buffer) {
int len = 4 * ((buffer.remaining() + 2) / 3); int len = outLength(buffer.remaining());
if (linemax > 0)
len += (len - 1) / linemax * newline.length;
byte[] dst = new byte[len]; byte[] dst = new byte[len];
int ret = 0; int ret = 0;
if (buffer.hasArray()) { if (buffer.hasArray()) {
...@@ -415,7 +423,25 @@ public class Base64 { ...@@ -415,7 +423,25 @@ public class Base64 {
public OutputStream wrap(OutputStream os) { public OutputStream wrap(OutputStream os) {
Objects.requireNonNull(os); Objects.requireNonNull(os);
return new EncOutputStream(os, isURL ? toBase64URL : toBase64, return new EncOutputStream(os, isURL ? toBase64URL : toBase64,
newline, linemax); newline, linemax, doPadding);
}
/**
* Returns an encoder instance that encodes equivalently to this one,
* but without adding any padding character at the end of the encoded
* byte data.
*
* <p> The encoding scheme of this encoder instance is unaffected by
* this invocation. The returned encoder instance should be used for
* non-padding encoding operation.
*
* @return an equivalent encoder that encodes without adding any
* padding character at the end
*/
public Encoder withoutPadding() {
if (!doPadding)
return this;
return new Encoder(isURL, newline, linemax, false);
} }
private int encodeArray(ByteBuffer src, ByteBuffer dst, int bytesOut) { private int encodeArray(ByteBuffer src, ByteBuffer dst, int bytesOut) {
...@@ -476,13 +502,17 @@ public class Base64 { ...@@ -476,13 +502,17 @@ public class Base64 {
da[dp++] = (byte)base64[b0 >> 2]; da[dp++] = (byte)base64[b0 >> 2];
if (sp == sl) { if (sp == sl) {
da[dp++] = (byte)base64[(b0 << 4) & 0x3f]; da[dp++] = (byte)base64[(b0 << 4) & 0x3f];
da[dp++] = '='; if (doPadding) {
da[dp++] = '='; da[dp++] = '=';
da[dp++] = '=';
}
} else { } else {
int b1 = sa[sp++] & 0xff; int b1 = sa[sp++] & 0xff;
da[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]; da[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
da[dp++] = (byte)base64[(b1 << 2) & 0x3f]; da[dp++] = (byte)base64[(b1 << 2) & 0x3f];
da[dp++] = '='; if (doPadding) {
da[dp++] = '=';
}
} }
} }
return dp - dp00 + bytesOut; return dp - dp00 + bytesOut;
...@@ -548,13 +578,17 @@ public class Base64 { ...@@ -548,13 +578,17 @@ public class Base64 {
dst.put(dp++, (byte)base64[b0 >> 2]); dst.put(dp++, (byte)base64[b0 >> 2]);
if (sp == src.limit()) { if (sp == src.limit()) {
dst.put(dp++, (byte)base64[(b0 << 4) & 0x3f]); dst.put(dp++, (byte)base64[(b0 << 4) & 0x3f]);
dst.put(dp++, (byte)'='); if (doPadding) {
dst.put(dp++, (byte)'='); dst.put(dp++, (byte)'=');
dst.put(dp++, (byte)'=');
}
} else { } else {
int b1 = src.get(sp++) & 0xff; int b1 = src.get(sp++) & 0xff;
dst.put(dp++, (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]); dst.put(dp++, (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
dst.put(dp++, (byte)base64[(b1 << 2) & 0x3f]); dst.put(dp++, (byte)base64[(b1 << 2) & 0x3f]);
dst.put(dp++, (byte)'='); if (doPadding) {
dst.put(dp++, (byte)'=');
}
} }
} }
return dp - dp00 + bytesOut; return dp - dp00 + bytesOut;
...@@ -597,13 +631,17 @@ public class Base64 { ...@@ -597,13 +631,17 @@ public class Base64 {
dst[dp++] = (byte)base64[b0 >> 2]; dst[dp++] = (byte)base64[b0 >> 2];
if (sp == end) { if (sp == end) {
dst[dp++] = (byte)base64[(b0 << 4) & 0x3f]; dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];
dst[dp++] = '='; if (doPadding) {
dst[dp++] = '='; dst[dp++] = '=';
dst[dp++] = '=';
}
} else { } else {
int b1 = src[sp++] & 0xff; int b1 = src[sp++] & 0xff;
dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]; dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
dst[dp++] = (byte)base64[(b1 << 2) & 0x3f]; dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];
dst[dp++] = '='; if (doPadding) {
dst[dp++] = '=';
}
} }
} }
return dp; return dp;
...@@ -1149,14 +1187,16 @@ public class Base64 { ...@@ -1149,14 +1187,16 @@ public class Base64 {
private final char[] base64; // byte->base64 mapping private final char[] base64; // byte->base64 mapping
private final byte[] newline; // line separator, if needed private final byte[] newline; // line separator, if needed
private final int linemax; private final int linemax;
private final boolean doPadding;// whether or not to pad
private int linepos = 0; private int linepos = 0;
EncOutputStream(OutputStream os, EncOutputStream(OutputStream os, char[] base64,
char[] base64, byte[] newline, int linemax) { byte[] newline, int linemax, boolean doPadding) {
super(os); super(os);
this.base64 = base64; this.base64 = base64;
this.newline = newline; this.newline = newline;
this.linemax = linemax; this.linemax = linemax;
this.doPadding = doPadding;
} }
@Override @Override
...@@ -1228,14 +1268,18 @@ public class Base64 { ...@@ -1228,14 +1268,18 @@ public class Base64 {
checkNewline(); checkNewline();
out.write(base64[b0 >> 2]); out.write(base64[b0 >> 2]);
out.write(base64[(b0 << 4) & 0x3f]); out.write(base64[(b0 << 4) & 0x3f]);
out.write('='); if (doPadding) {
out.write('='); out.write('=');
out.write('=');
}
} else if (leftover == 2) { } else if (leftover == 2) {
checkNewline(); checkNewline();
out.write(base64[b0 >> 2]); out.write(base64[b0 >> 2]);
out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
out.write(base64[(b1 << 2) & 0x3f]); out.write(base64[(b1 << 2) & 0x3f]);
out.write('='); if (doPadding) {
out.write('=');
}
} }
leftover = 0; leftover = 0;
out.close(); out.close();
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
/** /**
* @test 4235519 8004212 8005394 8007298 8006295 8006315 8006530 8007379 8008925 * @test 4235519 8004212 8005394 8007298 8006295 8006315 8006530 8007379 8008925
* 8014217 8025003 * 8014217 8025003 8026330
* @summary tests java.util.Base64 * @summary tests java.util.Base64
*/ */
...@@ -47,12 +47,9 @@ public class TestBase64 { ...@@ -47,12 +47,9 @@ public class TestBase64 {
numBytes = Integer.parseInt(args[1]); numBytes = Integer.parseInt(args[1]);
} }
test(Base64.getEncoder(), Base64.getDecoder(), test(Base64.getEncoder(), Base64.getDecoder(), numRuns, numBytes);
numRuns, numBytes); test(Base64.getUrlEncoder(), Base64.getUrlDecoder(), numRuns, numBytes);
test(Base64.getUrlEncoder(), Base64.getUrlDecoder(), test(Base64.getMimeEncoder(), Base64.getMimeDecoder(), numRuns, numBytes);
numRuns, numBytes);
test(Base64.getMimeEncoder(), Base64.getMimeDecoder(),
numRuns, numBytes);
Random rnd = new java.util.Random(); Random rnd = new java.util.Random();
byte[] nl_1 = new byte[] {'\n'}; byte[] nl_1 = new byte[] {'\n'};
...@@ -142,165 +139,175 @@ public class TestBase64 { ...@@ -142,165 +139,175 @@ public class TestBase64 {
enc.encode(new byte[0]); enc.encode(new byte[0]);
dec.decode(new byte[0]); dec.decode(new byte[0]);
for (int i=0; i<numRuns; i++) { for (boolean withoutPadding : new boolean[] { false, true}) {
for (int j=1; j<numBytes; j++) { if (withoutPadding) {
byte[] orig = new byte[j]; enc = enc.withoutPadding();
rnd.nextBytes(orig); }
for (int i=0; i<numRuns; i++) {
// --------testing encode/decode(byte[])-------- for (int j=1; j<numBytes; j++) {
byte[] encoded = enc.encode(orig); byte[] orig = new byte[j];
byte[] decoded = dec.decode(encoded); rnd.nextBytes(orig);
checkEqual(orig, decoded, // --------testing encode/decode(byte[])--------
"Base64 array encoding/decoding failed!"); byte[] encoded = enc.encode(orig);
byte[] decoded = dec.decode(encoded);
// compare to sun.misc.BASE64Encoder
byte[] encoded2 = sunmisc.encode(orig).getBytes("ASCII"); checkEqual(orig, decoded,
checkEqual(normalize(encoded), "Base64 array encoding/decoding failed!");
normalize(encoded2), if (withoutPadding) {
"Base64 enc.encode() does not match sun.misc.base64!"); if (encoded[encoded.length - 1] == '=')
throw new RuntimeException(
// remove padding '=' to test non-padding decoding case "Base64 enc.encode().withoutPadding() has padding!");
if (encoded[encoded.length -2] == '=') }
encoded2 = Arrays.copyOf(encoded, encoded.length -2); // compare to sun.misc.BASE64Encoder
else if (encoded[encoded.length -1] == '=')
encoded2 = Arrays.copyOf(encoded, encoded.length -1);
else
encoded2 = null;
// --------testing encodetoString(byte[])/decode(String)--------
String str = enc.encodeToString(orig);
if (!Arrays.equals(str.getBytes("ASCII"), encoded)) {
throw new RuntimeException(
"Base64 encodingToString() failed!");
}
byte[] buf = dec.decode(new String(encoded, "ASCII"));
checkEqual(buf, orig, "Base64 decoding(String) failed!");
if (encoded2 != null) { byte[] encoded2 = sunmisc.encode(orig).getBytes("ASCII");
buf = dec.decode(new String(encoded2, "ASCII")); if (!withoutPadding) { // don't test for withoutPadding()
checkEqual(normalize(encoded), normalize(encoded2),
"Base64 enc.encode() does not match sun.misc.base64!");
}
// remove padding '=' to test non-padding decoding case
if (encoded[encoded.length -2] == '=')
encoded2 = Arrays.copyOf(encoded, encoded.length -2);
else if (encoded[encoded.length -1] == '=')
encoded2 = Arrays.copyOf(encoded, encoded.length -1);
else
encoded2 = null;
// --------testing encodetoString(byte[])/decode(String)--------
String str = enc.encodeToString(orig);
if (!Arrays.equals(str.getBytes("ASCII"), encoded)) {
throw new RuntimeException(
"Base64 encodingToString() failed!");
}
byte[] buf = dec.decode(new String(encoded, "ASCII"));
checkEqual(buf, orig, "Base64 decoding(String) failed!"); checkEqual(buf, orig, "Base64 decoding(String) failed!");
}
//-------- testing encode/decode(Buffer)-------- if (encoded2 != null) {
testEncode(enc, ByteBuffer.wrap(orig), encoded); buf = dec.decode(new String(encoded2, "ASCII"));
ByteBuffer bin = ByteBuffer.allocateDirect(orig.length); checkEqual(buf, orig, "Base64 decoding(String) failed!");
bin.put(orig).flip();
testEncode(enc, bin, encoded);
testDecode(dec, ByteBuffer.wrap(encoded), orig);
bin = ByteBuffer.allocateDirect(encoded.length);
bin.put(encoded).flip();
testDecode(dec, bin, orig);
if (encoded2 != null)
testDecode(dec, ByteBuffer.wrap(encoded2), orig);
// -------- testing encode(Buffer, Buffer)--------
testEncode(enc, encoded,
ByteBuffer.wrap(orig),
ByteBuffer.allocate(encoded.length + 10));
testEncode(enc, encoded,
ByteBuffer.wrap(orig),
ByteBuffer.allocateDirect(encoded.length + 10));
// --------testing decode(Buffer, Buffer);--------
testDecode(dec, orig,
ByteBuffer.wrap(encoded),
ByteBuffer.allocate(orig.length + 10));
testDecode(dec, orig,
ByteBuffer.wrap(encoded),
ByteBuffer.allocateDirect(orig.length + 10));
// --------testing decode.wrap(input stream)--------
// 1) random buf length
ByteArrayInputStream bais = new ByteArrayInputStream(encoded);
InputStream is = dec.wrap(bais);
buf = new byte[orig.length + 10];
int len = orig.length;
int off = 0;
while (true) {
int n = rnd.nextInt(len);
if (n == 0)
n = 1;
n = is.read(buf, off, n);
if (n == -1) {
checkEqual(off, orig.length,
"Base64 stream decoding failed");
break;
} }
off += n;
len -= n;
if (len == 0)
break;
}
buf = Arrays.copyOf(buf, off);
checkEqual(buf, orig, "Base64 stream decoding failed!");
// 2) read one byte each
bais.reset();
is = dec.wrap(bais);
buf = new byte[orig.length + 10];
off = 0;
int b;
while ((b = is.read()) != -1) {
buf[off++] = (byte)b;
}
buf = Arrays.copyOf(buf, off);
checkEqual(buf, orig, "Base64 stream decoding failed!");
// --------testing encode.wrap(output stream)--------
ByteArrayOutputStream baos = new ByteArrayOutputStream((orig.length + 2) / 3 * 4 + 10);
OutputStream os = enc.wrap(baos);
off = 0;
len = orig.length;
for (int k = 0; k < 5; k++) {
if (len == 0)
break;
int n = rnd.nextInt(len);
if (n == 0)
n = 1;
os.write(orig, off, n);
off += n;
len -= n;
}
if (len != 0)
os.write(orig, off, len);
os.close();
buf = baos.toByteArray();
checkEqual(buf, encoded, "Base64 stream encoding failed!");
// 2) write one byte each
baos.reset();
os = enc.wrap(baos);
off = 0;
while (off < orig.length) {
os.write(orig[off++]);
}
os.close();
buf = baos.toByteArray();
checkEqual(buf, encoded, "Base64 stream encoding failed!");
// --------testing encode(in, out); -> bigger buf--------
buf = new byte[encoded.length + rnd.nextInt(100)];
int ret = enc.encode(orig, buf);
checkEqual(ret, encoded.length,
"Base64 enc.encode(src, null) returns wrong size!");
buf = Arrays.copyOf(buf, ret);
checkEqual(buf, encoded,
"Base64 enc.encode(src, dst) failed!");
// --------testing decode(in, out); -> bigger buf--------
buf = new byte[orig.length + rnd.nextInt(100)];
ret = dec.decode(encoded, buf);
checkEqual(ret, orig.length,
"Base64 enc.encode(src, null) returns wrong size!");
buf = Arrays.copyOf(buf, ret);
checkEqual(buf, orig,
"Base64 dec.decode(src, dst) failed!");
//-------- testing encode/decode(Buffer)--------
testEncode(enc, ByteBuffer.wrap(orig), encoded);
ByteBuffer bin = ByteBuffer.allocateDirect(orig.length);
bin.put(orig).flip();
testEncode(enc, bin, encoded);
testDecode(dec, ByteBuffer.wrap(encoded), orig);
bin = ByteBuffer.allocateDirect(encoded.length);
bin.put(encoded).flip();
testDecode(dec, bin, orig);
if (encoded2 != null)
testDecode(dec, ByteBuffer.wrap(encoded2), orig);
// -------- testing encode(Buffer, Buffer)--------
testEncode(enc, encoded,
ByteBuffer.wrap(orig),
ByteBuffer.allocate(encoded.length + 10));
testEncode(enc, encoded,
ByteBuffer.wrap(orig),
ByteBuffer.allocateDirect(encoded.length + 10));
// --------testing decode(Buffer, Buffer);--------
testDecode(dec, orig,
ByteBuffer.wrap(encoded),
ByteBuffer.allocate(orig.length + 10));
testDecode(dec, orig,
ByteBuffer.wrap(encoded),
ByteBuffer.allocateDirect(orig.length + 10));
// --------testing decode.wrap(input stream)--------
// 1) random buf length
ByteArrayInputStream bais = new ByteArrayInputStream(encoded);
InputStream is = dec.wrap(bais);
buf = new byte[orig.length + 10];
int len = orig.length;
int off = 0;
while (true) {
int n = rnd.nextInt(len);
if (n == 0)
n = 1;
n = is.read(buf, off, n);
if (n == -1) {
checkEqual(off, orig.length,
"Base64 stream decoding failed");
break;
}
off += n;
len -= n;
if (len == 0)
break;
}
buf = Arrays.copyOf(buf, off);
checkEqual(buf, orig, "Base64 stream decoding failed!");
// 2) read one byte each
bais.reset();
is = dec.wrap(bais);
buf = new byte[orig.length + 10];
off = 0;
int b;
while ((b = is.read()) != -1) {
buf[off++] = (byte)b;
}
buf = Arrays.copyOf(buf, off);
checkEqual(buf, orig, "Base64 stream decoding failed!");
// --------testing encode.wrap(output stream)--------
ByteArrayOutputStream baos = new ByteArrayOutputStream((orig.length + 2) / 3 * 4 + 10);
OutputStream os = enc.wrap(baos);
off = 0;
len = orig.length;
for (int k = 0; k < 5; k++) {
if (len == 0)
break;
int n = rnd.nextInt(len);
if (n == 0)
n = 1;
os.write(orig, off, n);
off += n;
len -= n;
}
if (len != 0)
os.write(orig, off, len);
os.close();
buf = baos.toByteArray();
checkEqual(buf, encoded, "Base64 stream encoding failed!");
// 2) write one byte each
baos.reset();
os = enc.wrap(baos);
off = 0;
while (off < orig.length) {
os.write(orig[off++]);
}
os.close();
buf = baos.toByteArray();
checkEqual(buf, encoded, "Base64 stream encoding failed!");
// --------testing encode(in, out); -> bigger buf--------
buf = new byte[encoded.length + rnd.nextInt(100)];
int ret = enc.encode(orig, buf);
checkEqual(ret, encoded.length,
"Base64 enc.encode(src, null) returns wrong size!");
buf = Arrays.copyOf(buf, ret);
checkEqual(buf, encoded,
"Base64 enc.encode(src, dst) failed!");
// --------testing decode(in, out); -> bigger buf--------
buf = new byte[orig.length + rnd.nextInt(100)];
ret = dec.decode(encoded, buf);
checkEqual(ret, orig.length,
"Base64 enc.encode(src, null) returns wrong size!");
buf = Arrays.copyOf(buf, ret);
checkEqual(buf, orig,
"Base64 dec.decode(src, dst) failed!");
}
} }
} }
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册