Fix for bug 429761
Checkin on behalf of Iaroslav Savytskyi
diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/oxm/conversion/Base64.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/oxm/conversion/Base64.java
index 62f9d9b..93dc0f6 100644
--- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/oxm/conversion/Base64.java
+++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/oxm/conversion/Base64.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998 -2014 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
@@ -9,7 +9,7 @@
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
- ******************************************************************************/
+ ******************************************************************************/
package org.eclipse.persistence.internal.oxm.conversion;
import java.util.BitSet;
@@ -20,122 +20,157 @@
*/
public class Base64 {
- private static BitSet BoundChar;
- private static BitSet EBCDICUnsafeChar;
- private static byte[] Base64EncMap;
- private static byte[] Base64DecMap;
- private static char[] UUEncMap;
- private static byte[] UUDecMap;
+ private static final byte[] Base64EncMap;
+ private static final byte[] Base64DecMap;
+
+ private static final byte PADDING = 127;
// Class Initializer
static {
- // rfc-2046 & rfc-2045: (bcharsnospace & token)
- // used for multipart codings
- BoundChar = new BitSet(256);
- for (int ch = '0'; ch <= '9'; ch++) {
- BoundChar.set(ch);
- }
- for (int ch = 'A'; ch <= 'Z'; ch++) {
- BoundChar.set(ch);
- }
- for (int ch = 'a'; ch <= 'z'; ch++) {
- BoundChar.set(ch);
- }
- BoundChar.set('+');
- BoundChar.set('_');
- BoundChar.set('-');
- BoundChar.set('.');
-
- // EBCDIC unsafe characters to be quoted in quoted-printable
- // See first NOTE in section 6.7 of rfc-2045
- EBCDICUnsafeChar = new BitSet(256);
- EBCDICUnsafeChar.set('!');
- EBCDICUnsafeChar.set('"');
- EBCDICUnsafeChar.set('#');
- EBCDICUnsafeChar.set('$');
- EBCDICUnsafeChar.set('@');
- EBCDICUnsafeChar.set('[');
- EBCDICUnsafeChar.set('\\');
- EBCDICUnsafeChar.set(']');
- EBCDICUnsafeChar.set('^');
- EBCDICUnsafeChar.set('`');
- EBCDICUnsafeChar.set('{');
- EBCDICUnsafeChar.set('|');
- EBCDICUnsafeChar.set('}');
- EBCDICUnsafeChar.set('~');
-
// rfc-2045: Base64 Alphabet
- byte[] map = { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' };
- Base64EncMap = map;
- Base64DecMap = new byte[128];
- for (int idx = 0; idx < Base64EncMap.length; idx++) {
- Base64DecMap[Base64EncMap[idx]] = (byte)idx;
+ Base64EncMap = initEncodeMap();
+ Base64DecMap = initDecodeMap();
+ }
+
+ private static byte[] initEncodeMap() {
+ byte[] map = new byte[64];
+ int i;
+ for (i = 0; i < 26; i++) {
+ map[i] = (byte) ('A' + i);
+ }
+ for (i = 26; i < 52; i++) {
+ map[i] = (byte) ('a' + (i - 26));
+ }
+ for (i = 52; i < 62; i++) {
+ map[i] = (byte) ('0' + (i - 52));
+ }
+ map[62] = '+';
+ map[63] = '/';
+
+ return map;
+ }
+
+ private static byte[] initDecodeMap() {
+ byte[] map = new byte[128];
+ int i;
+ for (i = 0; i < 128; i++) {
+ map[i] = -1;
}
- // uuencode'ing maps
- UUEncMap = new char[64];
- for (int idx = 0; idx < UUEncMap.length; idx++) {
- UUEncMap[idx] = (char)(idx + 0x20);
+ for (i = 'A'; i <= 'Z'; i++) {
+ map[i] = (byte) (i - 'A');
}
- UUDecMap = new byte[128];
- for (int idx = 0; idx < UUEncMap.length; idx++) {
- UUDecMap[UUEncMap[idx]] = (byte)idx;
+ for (i = 'a'; i <= 'z'; i++) {
+ map[i] = (byte) (i - 'a' + 26);
}
+ for (i = '0'; i <= '9'; i++) {
+ map[i] = (byte) (i - '0' + 52);
+ }
+ map['+'] = 62;
+ map['/'] = 63;
+ map['='] = PADDING;
+
+ return map;
}
/**
* Base64 constructor comment.
*/
- public Base64() {
- super();
+ private Base64() {
}
/**
+ * computes the length of binary data speculatively.
+ *
+ * <p>
+ * Our requirement is to create byte[] of the exact length to store the binary data.
+ * If we do this in a straight-forward way, it takes two passes over the data.
+ * Experiments show that this is a non-trivial overhead (35% or so is spent on
+ * the first pass in calculating the length.)
+ *
+ * <p>
+ * So the approach here is that we compute the length speculatively, without looking
+ * at the whole contents. The obtained speculative value is never less than the
+ * actual length of the binary data, but it may be bigger. So if the speculation
+ * goes wrong, we'll pay the cost of reallocation and buffer copying.
+ *
+ * <p>
+ * If the base64 text is tightly packed with no indentation nor illegal char
+ * (like what most web services produce), then the speculation of this method
+ * will be correct, so we get the performance benefit.
+ */
+ private static int guessLength(byte[] data) {
+ final int len = data.length;
+
+ // compute the tail '=' chars
+ int j = len - 1;
+ for (; j >= 0; j--) {
+ byte code = Base64DecMap[data[j]];
+ if (code == PADDING)
+ continue;
+ if (code == -1) // most likely this base64 data is indented. go with the upper bound
+ return data.length / 4 * 3;
+ break;
+ }
+
+ j++; // data.charAt(j) is now at some base64 char, so +1 to make it the size
+ int padSize = len - j;
+ if (padSize > 2) // something is wrong with base64. be safe and go with the upper bound
+ return data.length / 4 * 3;
+
+ // so far this base64 looks like it's unindented tightly packed base64.
+ // take a chance and create an array with the expected size
+ return data.length / 4 * 3 - padSize;
+ }
+
+ /**
+ * base64Binary data is likely to be long, and decoding requires
+ * each character to be accessed twice (once for counting length, another
+ * for decoding.)
+ *
* This method decodes the given byte[] using the base64-encoding
* specified in RFC-2045 (Section 6.8).
*
* @param data the base64-encoded data.
* @return the decoded <var>data</var>.
*/
- public final static byte[] base64Decode(byte[] data) {
- if (data == null) {
- return null;
- }
-
- if (data.length == 0) {
- return new byte[0];
+ public static byte[] base64Decode(byte[] data) {
+ final int buflen = guessLength(data);
+ final byte[] out = new byte[buflen];
+ int o = 0;
+
+ final int len = data.length;
+ int i;
+
+ final byte[] quadruplet = new byte[4];
+ int q = 0;
+
+ // convert each quadruplet to three bytes.
+ for (i = 0; i < len; i++) {
+ byte ch = data[i];
+ byte v = Base64DecMap[ch];
+
+ if (v != -1)
+ quadruplet[q++] = v;
+
+ if (q == 4) {
+ // quadruplet is now filled.
+ out[o++] = (byte) ((quadruplet[0] << 2) | (quadruplet[1] >> 4));
+ if (quadruplet[2] != PADDING)
+ out[o++] = (byte) ((quadruplet[1] << 4) | (quadruplet[2] >> 2));
+ if (quadruplet[3] != PADDING)
+ out[o++] = (byte) ((quadruplet[2] << 6) | (quadruplet[3]));
+ q = 0;
+ }
}
- int tail = data.length;
- while (data[tail - 1] == '=') {
- tail--;
- }
+ if (buflen == o) // speculation worked out to be OK
+ return out;
- byte[] dest = new byte[tail - (data.length / 4)];
-
- // ascii printable to 0-63 conversion
- for (int idx = 0; idx < data.length; idx++) {
- data[idx] = Base64DecMap[data[idx]];
- }
-
- // 4-byte to 3-byte conversion
- int sidx;
-
- // 4-byte to 3-byte conversion
- int didx;
- for (sidx = 0, didx = 0; didx < (dest.length - 2); sidx += 4, didx += 3) {
- dest[didx] = (byte)(((data[sidx] << 2) & 255) | ((data[sidx + 1] >>> 4) & 003));
- dest[didx + 1] = (byte)(((data[sidx + 1] << 4) & 255) | ((data[sidx + 2] >>> 2) & 017));
- dest[didx + 2] = (byte)(((data[sidx + 2] << 6) & 255) | (data[sidx + 3] & 077));
- }
- if (didx < dest.length) {
- dest[didx] = (byte)(((data[sidx] << 2) & 255) | ((data[sidx + 1] >>> 4) & 003));
- }
- if (++didx < dest.length) {
- dest[didx] = (byte)(((data[sidx + 1] << 4) & 255) | ((data[sidx + 2] >>> 2) & 017));
- }
-
- return dest;
+ // we overestimated, so need to create a new buffer
+ byte[] nb = new byte[o];
+ System.arraycopy(out, 0, nb, 0, o);
+ return nb;
}
/**
@@ -145,7 +180,7 @@
* @param data the data
* @return the base64-encoded <var>data</var>
*/
- public final static byte[] base64Encode(byte[] data) {
+ public static byte[] base64Encode(byte[] data) {
if (data == null) {
return null;
}
diff --git a/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/jaxbelement/simple/JAXBElementBase64TestCases.java b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/jaxbelement/simple/JAXBElementBase64TestCases.java
index 7783f3d..04125e8 100644
--- a/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/jaxbelement/simple/JAXBElementBase64TestCases.java
+++ b/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/jaxbelement/simple/JAXBElementBase64TestCases.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998 - 2014 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
@@ -14,9 +14,11 @@
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
+import org.eclipse.persistence.internal.oxm.conversion.Base64;
import org.eclipse.persistence.testing.jaxb.jaxbelement.JAXBElementTestCases;
@@ -63,4 +65,8 @@
super.testSchemaGen(new ArrayList<InputStream>());
}
+ public void testInvalidData() throws Exception {
+ byte[] result = Base64.base64Decode("cid:1197646757481".getBytes());
+ assertTrue(Arrays.equals("cid1197646757481".getBytes(), Base64.base64Encode(result)));
+ }
}