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'}));
+ }
+}