This is an automated email from the ASF dual-hosted git repository.

reta pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/main by this push:
     new 24dfd0bcb71 validate hex digits when decoding quoted-printable 
attachments (#3223)
24dfd0bcb71 is described below

commit 24dfd0bcb71d4695552dc4bea41b89aa6e47b6f6
Author: Javid Khan <[email protected]>
AuthorDate: Fri Jun 19 02:01:31 2026 +0530

    validate hex digits when decoding quoted-printable attachments (#3223)
    
    * validate hex digits when decoding quoted-printable attachments
    
    * use HexFormat to decode quoted-printable hex pairs
    
    * use StandardCharsets.US_ASCII in quoted-printable decoder test
---
 .../attachment/QuotedPrintableDecoderStream.java   | 31 ++++------
 .../QuotedPrintableDecoderStreamTest.java          | 67 ++++++++++++++++++++++
 2 files changed, 78 insertions(+), 20 deletions(-)

diff --git 
a/core/src/main/java/org/apache/cxf/attachment/QuotedPrintableDecoderStream.java
 
b/core/src/main/java/org/apache/cxf/attachment/QuotedPrintableDecoderStream.java
index f58e1a14061..6e4569d8ce0 100644
--- 
a/core/src/main/java/org/apache/cxf/attachment/QuotedPrintableDecoderStream.java
+++ 
b/core/src/main/java/org/apache/cxf/attachment/QuotedPrintableDecoderStream.java
@@ -20,25 +20,9 @@ package org.apache.cxf.attachment;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.HexFormat;
 
 public class QuotedPrintableDecoderStream extends InputStream {
-    private static final byte[] ENCODING_TABLE = {
-        (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', 
(byte)'6', (byte)'7', (byte)'8',
-        (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', 
(byte)'F'
-    };
-
-    /*
-     * set up the decoding table.
-     */
-    private static final byte[] DECODING_TABLE = new byte[128];
-
-    static {
-        // initialize the decoding table
-        for (int i = 0; i < ENCODING_TABLE.length; i++) {
-            DECODING_TABLE[ENCODING_TABLE[i]] = (byte)i;
-        }
-    }
-
     private int deferredWhitespace;
     private int cachedCharacter = -1;
     private final InputStream in;
@@ -66,9 +50,16 @@ public class QuotedPrintableDecoderStream extends 
InputStream {
             return read();
         }
         // this is a hex pair we need to convert back to a single byte.
-        b[0] = DECODING_TABLE[b[0]];
-        b[1] = DECODING_TABLE[b[1]];
-        return (b[0] << 4) | b[1];
+        return (decodeHexDigit(b[0]) << 4) | decodeHexDigit(b[1]);
+    }
+
+    private static int decodeHexDigit(byte b) throws IOException {
+        // mask to an unsigned value first so a high-bit byte is treated as a 
codepoint rather than indexing negatively
+        try {
+            return HexFormat.fromHexDigit(b & 0xff);
+        } catch (NumberFormatException e) {
+            throw new IOException("Invalid quoted printable encoding", e);
+        }
     }
 
     @Override
diff --git 
a/core/src/test/java/org/apache/cxf/attachment/QuotedPrintableDecoderStreamTest.java
 
b/core/src/test/java/org/apache/cxf/attachment/QuotedPrintableDecoderStreamTest.java
new file mode 100644
index 00000000000..9e2ea016bbc
--- /dev/null
+++ 
b/core/src/test/java/org/apache/cxf/attachment/QuotedPrintableDecoderStreamTest.java
@@ -0,0 +1,67 @@
+/**
+ * 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.cxf.attachment;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertThrows;
+
+public class QuotedPrintableDecoderStreamTest {
+
+    private static byte[] decode(byte[] encoded) throws IOException {
+        InputStream in = new QuotedPrintableDecoderStream(new 
ByteArrayInputStream(encoded));
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        int b;
+        while ((b = in.read()) != -1) {
+            out.write(b);
+        }
+        return out.toByteArray();
+    }
+
+    @Test
+    public void testDecodesHexPair() throws Exception {
+        assertArrayEquals("hello world".getBytes(StandardCharsets.US_ASCII),
+                          
decode("hello=20world".getBytes(StandardCharsets.US_ASCII)));
+    }
+
+    @Test
+    public void testDecodesLowerCaseHex() throws Exception {
+        // a lower case hex pair must decode to the same byte as the upper 
case form
+        assertArrayEquals(new byte[] {(byte)0xe2}, 
decode("=e2".getBytes(StandardCharsets.US_ASCII)));
+        assertArrayEquals(new byte[] {(byte)0xe2}, 
decode("=E2".getBytes(StandardCharsets.US_ASCII)));
+    }
+
+    @Test
+    public void testRejectsNonHexCharacters() {
+        assertThrows(IOException.class, () -> 
decode("=GG".getBytes(StandardCharsets.US_ASCII)));
+    }
+
+    @Test
+    public void testRejectsHighBitByteAfterMarker() {
+        // a byte >= 0x80 following the '=' marker must not index the decode 
table negatively
+        assertThrows(IOException.class, () -> decode(new byte[] {'=', 
(byte)0xff, 'A'}));
+    }
+}

Reply via email to