Author: fanningpj
Date: Tue Jun 16 17:12:03 2026
New Revision: 1935422

Log:
fix out-of-bounds read in scanAttribute for valueless xml decl attr. Thanks to 
aizu-m. This closes #52

Added:
   xmlbeans/trunk/src/test/java/misc/checkin/SniffedXmlEncodingTest.java
Modified:
   
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/common/SniffedXmlInputStream.java
   
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/common/SniffedXmlReader.java

Modified: 
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/common/SniffedXmlInputStream.java
==============================================================================
--- 
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/common/SniffedXmlInputStream.java
     Tue Jun 16 17:08:51 2026        (r1935421)
+++ 
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/common/SniffedXmlInputStream.java
     Tue Jun 16 17:12:03 2026        (r1935422)
@@ -86,7 +86,6 @@ public class SniffedXmlInputStream exten
 
     private String sniffFourBytes() throws IOException {
         mark(4);
-        int skip = 0;
         try {
             byte[] buf = new byte[4];
             if (readAsMuchAsPossible(buf, 0, 4) < 4) {
@@ -124,37 +123,25 @@ public class SniffedXmlInputStream exten
         }
     }
 
-    // BUGBUG in JDK: Charset.forName is not threadsafe, so we'll prime it
-    // with the common charsets.
-
-    private static Charset dummy1 = Charset.forName("UTF-8");
-    private static Charset dummy2 = Charset.forName("UTF-16");
-    private static Charset dummy3 = Charset.forName("UTF-16BE");
-    private static Charset dummy4 = Charset.forName("UTF-16LE");
-    private static Charset dummy5 = Charset.forName("ISO-8859-1");
-    private static Charset dummy6 = Charset.forName("US-ASCII");
-    private static Charset dummy7 = Charset.forName("Cp1252");
-
-
     private String sniffForXmlDecl(String encoding) throws IOException {
         mark(MAX_SNIFFED_BYTES);
         try {
             byte[] bytebuf = new byte[MAX_SNIFFED_BYTES];
             int bytelimit = readAsMuchAsPossible(bytebuf, 0, 
MAX_SNIFFED_BYTES);
 
-            // BUGBUG in JDK: Charset.forName is not threadsafe.
             Charset charset = Charset.forName(encoding);
-            Reader reader = new InputStreamReader(new 
ByteArrayInputStream(bytebuf, 0, bytelimit), charset);
-            char[] buf = new char[bytelimit];
             int limit = 0;
-            while (limit < bytelimit) {
-                int count = reader.read(buf, limit, bytelimit - limit);
-                if (count < 0) {
-                    break;
+            char[] buf = new char[bytelimit];
+            try (Reader reader = new InputStreamReader(
+                    new ByteArrayInputStream(bytebuf, 0, bytelimit), charset)) 
{
+                while (limit < bytelimit) {
+                    int count = reader.read(buf, limit, bytelimit - limit);
+                    if (count < 0) {
+                        break;
+                    }
+                    limit += count;
                 }
-                limit += count;
             }
-
             return extractXmlDeclEncoding(buf, 0, limit);
         } finally {
             reset();
@@ -222,7 +209,6 @@ public class SniffedXmlInputStream exten
     }
 
     private static int nextMatchingByte(char[] lookFor, char[] buf, int 
startAt, int limit) {
-        searching:
         for (; startAt < limit; startAt++) {
             int thischar = buf[startAt];
             for (int i = 0; i < lookFor.length; i++) {
@@ -235,7 +221,6 @@ public class SniffedXmlInputStream exten
     }
 
     private static int nextMatchingByte(char lookFor, char[] buf, int startAt, 
int limit) {
-        searching:
         for (; startAt < limit; startAt++) {
             if (buf[startAt] == lookFor) {
                 return startAt;
@@ -244,8 +229,8 @@ public class SniffedXmlInputStream exten
         return -1;
     }
 
-    private static char[] WHITESPACE = new char[]{' ', '\r', '\t', '\n'};
-    private static char[] NOTNAME = new char[]{'=', ' ', '\r', '\t', '\n', 
'?', '>', '<', '\'', '\"'};
+    private static final char[] WHITESPACE = new char[]{' ', '\r', '\t', '\n'};
+    private static final char[] NOTNAME = new char[]{'=', ' ', '\r', '\t', 
'\n', '?', '>', '<', '\'', '\"'};
 
     private static class ScannedAttribute {
         public String name;
@@ -269,6 +254,9 @@ public class SniffedXmlInputStream exten
             return -1;
         }
         int valQuote = nextNonmatchingByte(WHITESPACE, buf, equals + 1, limit);
+        if (valQuote < 0) {
+            return -1;
+        }
         if (buf[valQuote] != '\'' && buf[valQuote] != '\"') {
             return -1;
         }

Modified: 
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/common/SniffedXmlReader.java
==============================================================================
--- 
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/common/SniffedXmlReader.java
  Tue Jun 16 17:08:51 2026        (r1935421)
+++ 
xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/common/SniffedXmlReader.java
  Tue Jun 16 17:12:03 2026        (r1935422)
@@ -18,7 +18,6 @@ package org.apache.xmlbeans.impl.common;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.Reader;
-import java.nio.charset.Charset;
 
 public class SniffedXmlReader extends BufferedReader {
     // We don't sniff more than 192 bytes.
@@ -41,18 +40,6 @@ public class SniffedXmlReader extends Bu
         return total;
     }
 
-    // BUGBUG in JDK: Charset.forName is not threadsafe, so we'll prime it
-    // with the common charsets.
-
-    private static Charset dummy1 = Charset.forName("UTF-8");
-    private static Charset dummy2 = Charset.forName("UTF-16");
-    private static Charset dummy3 = Charset.forName("UTF-16BE");
-    private static Charset dummy4 = Charset.forName("UTF-16LE");
-    private static Charset dummy5 = Charset.forName("ISO-8859-1");
-    private static Charset dummy6 = Charset.forName("US-ASCII");
-    private static Charset dummy7 = Charset.forName("Cp1252");
-
-
     private String sniffForXmlDecl() throws IOException {
         mark(MAX_SNIFFED_CHARS);
         try {

Added: xmlbeans/trunk/src/test/java/misc/checkin/SniffedXmlEncodingTest.java
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ xmlbeans/trunk/src/test/java/misc/checkin/SniffedXmlEncodingTest.java       
Tue Jun 16 17:12:03 2026        (r1935422)
@@ -0,0 +1,53 @@
+/*   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed 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.common.SniffedXmlInputStream;
+import org.apache.xmlbeans.impl.common.SniffedXmlReader;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class SniffedXmlEncodingTest {
+
+    // An xml declaration pseudo-attribute ending in '=' with no value, only
+    // whitespace up to the end of the sniffed buffer, used to index buf[-1]
+    // while scanning for the opening quote. The sniffer must report "no
+    // encoding found" instead of throwing ArrayIndexOutOfBoundsException.
+    @Test
+    void valuelessAttributeReader() throws Exception {
+        for (String s : new String[]{"<?xml x=", "<?xml version=\"1.0\" 
encoding=  ", "<?xml a=   \t\n"}) {
+            assertNull(new SniffedXmlReader(new 
StringReader(s)).getXmlEncoding());
+        }
+    }
+
+    @Test
+    void valuelessAttributeStream() throws Exception {
+        byte[] b = "<?xml version=\"1.0\" encoding= 
".getBytes(StandardCharsets.US_ASCII);
+        assertEquals("UTF-8", new SniffedXmlInputStream(new 
ByteArrayInputStream(b)).getXmlEncoding());
+    }
+
+    @Test
+    void wellFormedDeclarationStillDetected() throws Exception {
+        byte[] b = "<?xml version='1.0' 
encoding='ISO-8859-1'?>".getBytes(StandardCharsets.US_ASCII);
+        assertEquals("ISO-8859-1", new SniffedXmlInputStream(new 
ByteArrayInputStream(b)).getXmlEncoding());
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to