diff --git a/src/main/java/org/apache/xmlbeans/impl/richParser/XMLStreamReaderExtImpl.java b/src/main/java/org/apache/xmlbeans/impl/richParser/XMLStreamReaderExtImpl.java index a6ce66a1e..8adb49e24 100644 --- a/src/main/java/org/apache/xmlbeans/impl/richParser/XMLStreamReaderExtImpl.java +++ b/src/main/java/org/apache/xmlbeans/impl/richParser/XMLStreamReaderExtImpl.java @@ -22,6 +22,7 @@ import org.apache.xmlbeans.impl.common.InvalidLexicalValueException; import org.apache.xmlbeans.impl.common.XMLChar; import org.apache.xmlbeans.impl.common.XmlWhitespace; +import org.apache.xmlbeans.impl.util.Base64Bin; import org.apache.xmlbeans.impl.util.HexBin; import org.apache.xmlbeans.impl.util.XsTypeConverter; @@ -35,7 +36,6 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.nio.charset.StandardCharsets; -import java.util.Base64; import java.util.Date; /** @@ -182,11 +182,11 @@ public InputStream getBase64Value() throws XMLStreamException, InvalidLexicalValueException { _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM); String text = _charSeq.toString(); - try { - byte[] buf = Base64.getMimeDecoder().decode(text.getBytes(StandardCharsets.ISO_8859_1)); + byte[] buf = Base64Bin.decode(text); + if (buf != null) { return new ByteArrayInputStream(buf); - } catch (IllegalArgumentException e) { - throw new InvalidLexicalValueException("invalid base64Binary value", e, _charSeq.getLocation()); + } else { + throw new InvalidLexicalValueException("invalid base64Binary value", _charSeq.getLocation()); } } @@ -332,11 +332,11 @@ public InputStream getAttributeHexBinaryValue(int index) throws XMLStreamExcepti public InputStream getAttributeBase64Value(int index) throws XMLStreamException { String text = _charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM).toString(); - try { - byte[] buf = Base64.getMimeDecoder().decode(text.getBytes(StandardCharsets.ISO_8859_1)); + byte[] buf = Base64Bin.decode(text); + if (buf != null) { return new ByteArrayInputStream(buf); - } catch (IllegalArgumentException e) { - throw new InvalidLexicalValueException("invalid base64Binary value", e, _charSeq.getLocation()); + } else { + throw new InvalidLexicalValueException("invalid base64Binary value", _charSeq.getLocation()); } } @@ -486,11 +486,11 @@ public InputStream getAttributeHexBinaryValue(String uri, String local) throws X public InputStream getAttributeBase64Value(String uri, String local) throws XMLStreamException { CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM); String text = cs.toString(); - try { - byte[] buf = Base64.getMimeDecoder().decode(text.getBytes(StandardCharsets.ISO_8859_1)); + byte[] buf = Base64Bin.decode(text); + if (buf != null) { return new ByteArrayInputStream(buf); - } catch (IllegalArgumentException e) { - throw new InvalidLexicalValueException("invalid base64Binary value", e, _charSeq.getLocation()); + } else { + throw new InvalidLexicalValueException("invalid base64Binary value", _charSeq.getLocation()); } } diff --git a/src/main/java/org/apache/xmlbeans/impl/util/Base64Bin.java b/src/main/java/org/apache/xmlbeans/impl/util/Base64Bin.java new file mode 100644 index 000000000..ca756e698 --- /dev/null +++ b/src/main/java/org/apache/xmlbeans/impl/util/Base64Bin.java @@ -0,0 +1,69 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.xmlbeans.impl.util; + +import org.apache.xmlbeans.impl.common.XMLChar; + +import java.util.Base64; + +/** + * Strict decoding of the xsd:base64Binary lexical space. + *
+ * This class is for internal use in Apache XMLBeans. If you need to do your own base64 + * encoding/decoding, use {@link java.util.Base64}. + *
+ */ +public final class Base64Bin { + + private Base64Bin() { + } + + /** + * Decodes an xsd:base64Binary value. XML whitespace is ignored; anything else + * must be in the base64 alphabet, in groups of four characters with + * {@code =} padding only at the end. + *
+ * The JDK MIME decoder is deliberately not used: it silently drops characters
+ * outside the alphabet and accepts unpadded input, both of which fall outside
+ * the base64Binary lexical space.
+ *
+ * @param value the lexical value
+ * @return decoded bytes, or null if the input is null or not valid base64Binary
+ */
+ public static byte[] decode(String value) {
+ if (value == null) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder(value.length());
+ for (int i = 0, len = value.length(); i < len; i++) {
+ char ch = value.charAt(i);
+ if (!XMLChar.isSpace(ch)) {
+ sb.append(ch);
+ }
+ }
+ if (sb.length() % 4 != 0) {
+ return null;
+ }
+ try {
+ // the basic decoder rejects any char outside the alphabet and
+ // misplaced padding
+ return Base64.getDecoder().decode(sb.toString());
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaBase64Holder.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaBase64Holder.java
index d6d3ca35b..8acdd2c87 100644
--- a/src/main/java/org/apache/xmlbeans/impl/values/JavaBase64Holder.java
+++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaBase64Holder.java
@@ -21,8 +21,8 @@
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.impl.common.QNameHelper;
import org.apache.xmlbeans.impl.common.ValidationContext;
-import org.apache.xmlbeans.impl.common.XMLChar;
import org.apache.xmlbeans.impl.schema.BuiltinSchemaTypeSystem;
+import org.apache.xmlbeans.impl.util.Base64Bin;
import java.util.Arrays;
import java.util.Base64;
@@ -54,29 +54,12 @@ protected void set_nil() {
}
public static byte[] lex(String v, ValidationContext c) {
- // The MIME decoder silently discards any character outside the base64
- // alphabet, so a value carrying stray characters decodes to a truncated
- // result instead of being rejected. The base64Binary lexical space only
- // permits the alphabet and XML whitespace, so reject anything else here.
- for (int i = 0, len = v.length(); i < len; i++) {
- char ch = v.charAt(i);
- if (!isBase64Char(ch) && !XMLChar.isSpace(ch)) {
- c.invalid(XmlErrorCodes.BASE64BINARY, new Object[]{"not encoded properly"});
- return null;
- }
- }
- try {
- return Base64.getMimeDecoder().decode(v);
- } catch (IllegalArgumentException e) {
+ byte[] bytes = Base64Bin.decode(v);
+ if (bytes == null) {
// TODO - get a decent error with line numbers and such here
c.invalid(XmlErrorCodes.BASE64BINARY, new Object[]{"not encoded properly"});
- return null;
}
- }
-
- private static boolean isBase64Char(char ch) {
- return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
- (ch >= '0' && ch <= '9') || ch == '+' || ch == '/' || ch == '=';
+ return bytes;
}
public static byte[] validateLexical(String v, SchemaType sType, ValidationContext context) {
diff --git a/src/test/java/misc/checkin/Base64BinTest.java b/src/test/java/misc/checkin/Base64BinTest.java
new file mode 100644
index 000000000..81b15f0ed
--- /dev/null
+++ b/src/test/java/misc/checkin/Base64BinTest.java
@@ -0,0 +1,61 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package misc.checkin;
+
+import org.apache.xmlbeans.impl.util.Base64Bin;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class Base64BinTest {
+
+ private static final byte[] HELLO = "Hello".getBytes(StandardCharsets.US_ASCII);
+
+ @Test
+ void decodesValidValues() {
+ assertArrayEquals(HELLO, Base64Bin.decode("SGVsbG8="));
+ assertArrayEquals(HELLO, Base64Bin.decode(" SGVs bG8= "));
+ assertArrayEquals(HELLO, Base64Bin.decode("SGVs\r\n\tbG8="));
+ assertArrayEquals(new byte[]{'H'}, Base64Bin.decode("SA=="));
+ assertArrayEquals(new byte[0], Base64Bin.decode(""));
+ assertArrayEquals(new byte[0], Base64Bin.decode(" \n "));
+ }
+
+ @Test
+ void rejectsInvalidValues() {
+ assertNull(Base64Bin.decode(null));
+ // chars outside the alphabet
+ assertNull(Base64Bin.decode("SGVsbG8=!!!!"));
+ assertNull(Base64Bin.decode("SGV!!!sbG8="));
+ assertNull(Base64Bin.decode("!!!!"));
+ // URL-safe alphabet is not base64Binary
+ assertNull(Base64Bin.decode("-_-_"));
+ // missing padding / wrong length
+ assertNull(Base64Bin.decode("SGVsbG8"));
+ assertNull(Base64Bin.decode("SGVsbG"));
+ assertNull(Base64Bin.decode("A"));
+ // misplaced or excess padding
+ assertNull(Base64Bin.decode("SG=VsbG8="));
+ assertNull(Base64Bin.decode("SGVsbG8=SGVs"));
+ assertNull(Base64Bin.decode("SGVsbG8=="));
+ assertNull(Base64Bin.decode("S==="));
+ assertNull(Base64Bin.decode("===="));
+ }
+}
diff --git a/src/test/java/misc/checkin/Base64BinaryValidateTest.java b/src/test/java/misc/checkin/Base64BinaryValidateTest.java
index 1873e8159..5cb85eaca 100644
--- a/src/test/java/misc/checkin/Base64BinaryValidateTest.java
+++ b/src/test/java/misc/checkin/Base64BinaryValidateTest.java
@@ -20,6 +20,7 @@
import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -27,13 +28,20 @@
public class Base64BinaryValidateTest {
+ private static final String XSD =
+ "