From a64f81f886b0986332bbf6f6fd772a46e81d4cd4 Mon Sep 17 00:00:00 2001 From: Simon Bennetts Date: Mon, 16 Mar 2026 10:35:21 +0000 Subject: [PATCH] Common Codec PR fixes Merged https://github.com/zaproxy/zaproxy/pull/9282 to soon, so fixes for the outstanding comments Signed-off-by: Simon Bennetts --- LEGALNOTICE.md | 1 + buildSrc/build.gradle.kts | 1 + .../zap/tasks/CreateGitHubRelease.java | 28 ++--- .../zap/tasks/HandleWeeklyRelease.java | 17 +-- .../zap/tasks/UploadAssetsGitHubRelease.java | 29 ++--- .../org/zaproxy/zap/tasks/internal/Utils.java | 11 +- .../org/apache/commons/httpclient/URI.java | 104 +++++++++++++----- zap/zap.gradle.kts | 9 +- 8 files changed, 111 insertions(+), 89 deletions(-) diff --git a/LEGALNOTICE.md b/LEGALNOTICE.md index 2f8bfd62877..ee05ff2a85c 100644 --- a/LEGALNOTICE.md +++ b/LEGALNOTICE.md @@ -32,6 +32,7 @@ and subject to their respective licenses. | Library | License | |-------------------------------------|---------------------------| | commons-beanutils-1.11.0.jar | Apache 2.0 | +| commons-codec-1.20.0.jar | Apache 2.0 | | commons-collections-3.2.2.jar | Apache 2.0 | | commons-configuration-1.10.jar | Apache 2.0 | | commons-csv-1.14.1.jar | Apache 2.0 | diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 2ac59570ccb..98507b9b546 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -24,6 +24,7 @@ tasks.withType().configureEach { dependencies { implementation("com.github.javaparser:javaparser-core:3.15.21") implementation("com.github.zafarkhaja:java-semver:0.9.0") + implementation("commons-codec:commons-codec:1.20.0") implementation("commons-configuration:commons-configuration:1.10") implementation("commons-collections:commons-collections:3.2.2") implementation("commons-io:commons-io:2.13.0") diff --git a/buildSrc/src/main/java/org/zaproxy/zap/tasks/CreateGitHubRelease.java b/buildSrc/src/main/java/org/zaproxy/zap/tasks/CreateGitHubRelease.java index b469c974096..6efa48c8301 100644 --- a/buildSrc/src/main/java/org/zaproxy/zap/tasks/CreateGitHubRelease.java +++ b/buildSrc/src/main/java/org/zaproxy/zap/tasks/CreateGitHubRelease.java @@ -24,12 +24,10 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; -import java.util.HexFormat; import java.util.stream.Collectors; +import org.zaproxy.zap.tasks.internal.Utils; import org.gradle.api.Action; import org.gradle.api.DefaultTask; import org.gradle.api.InvalidUserDataException; @@ -228,21 +226,15 @@ private void appendChecksumsTable(StringBuilder body) throws IOException { .collect(Collectors.toList()); for (File file : files) { String fileName = file.getName(); - try { - byte[] digest = - MessageDigest.getInstance(algorithm) - .digest(Files.readAllBytes(file.toPath())); - body.append("| [") - .append(fileName) - .append("](") - .append(baseDownloadLink) - .append(fileName) - .append(") | `") - .append(HexFormat.of().formatHex(digest)) - .append("` |\n"); - } catch (NoSuchAlgorithmException e) { - throw new IOException("Unsupported digest algorithm: " + algorithm, e); - } + String hexDigest = Utils.digest(file.toPath(), algorithm); + body.append("| [") + .append(fileName) + .append("](") + .append(baseDownloadLink) + .append(fileName) + .append(") | `") + .append(hexDigest) + .append("` |\n"); } } } diff --git a/buildSrc/src/main/java/org/zaproxy/zap/tasks/HandleWeeklyRelease.java b/buildSrc/src/main/java/org/zaproxy/zap/tasks/HandleWeeklyRelease.java index 2c65b55bafb..5542e7b1319 100644 --- a/buildSrc/src/main/java/org/zaproxy/zap/tasks/HandleWeeklyRelease.java +++ b/buildSrc/src/main/java/org/zaproxy/zap/tasks/HandleWeeklyRelease.java @@ -25,11 +25,9 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; -import java.util.HexFormat; +import org.zaproxy.zap.tasks.internal.Utils; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -66,7 +64,7 @@ public HandleWeeklyRelease() { private void createPayloadData() { String checksum; try { - checksum = createChecksum(getChecksumAlgorithm().get(), downloadRelease()); + checksum = Utils.digest(downloadRelease(), getChecksumAlgorithm().get()); } catch (Exception e) { throw new BuildException(e); } @@ -107,15 +105,4 @@ private static String extractFileName(String url) { } return url.substring(idx + 1); } - - private static String createChecksum(String algorithm, Path file) throws IOException { - try { - byte[] digest = - MessageDigest.getInstance(algorithm) - .digest(Files.readAllBytes(file)); - return HexFormat.of().formatHex(digest); - } catch (NoSuchAlgorithmException e) { - throw new IOException("Unsupported digest algorithm: " + algorithm, e); - } - } } diff --git a/buildSrc/src/main/java/org/zaproxy/zap/tasks/UploadAssetsGitHubRelease.java b/buildSrc/src/main/java/org/zaproxy/zap/tasks/UploadAssetsGitHubRelease.java index 4eb515c2633..4f05c38d088 100644 --- a/buildSrc/src/main/java/org/zaproxy/zap/tasks/UploadAssetsGitHubRelease.java +++ b/buildSrc/src/main/java/org/zaproxy/zap/tasks/UploadAssetsGitHubRelease.java @@ -21,13 +21,10 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; -import java.util.HexFormat; import java.util.stream.Collectors; +import org.zaproxy.zap.tasks.internal.Utils; import org.gradle.api.Action; import org.gradle.api.DefaultTask; import org.gradle.api.InvalidUserDataException; @@ -185,21 +182,15 @@ private String updateChecksumsTable(String previousBody) throws IOException { .collect(Collectors.toList()); for (File file : files) { String fileName = file.getName(); - try { - byte[] digest = - MessageDigest.getInstance(algorithm) - .digest(Files.readAllBytes(file.toPath())); - body.append("| [") - .append(fileName) - .append("](") - .append(baseDownloadLink) - .append(fileName) - .append(") | `") - .append(HexFormat.of().formatHex(digest)) - .append("` |\n"); - } catch (NoSuchAlgorithmException e) { - throw new IOException("Unsupported digest algorithm: " + algorithm, e); - } + String hexDigest = Utils.digest(file.toPath(), algorithm); + body.append("| [") + .append(fileName) + .append("](") + .append(baseDownloadLink) + .append(fileName) + .append(") | `") + .append(hexDigest) + .append("` |\n"); } body.append(previousBody.substring(idx)); return body.toString(); diff --git a/buildSrc/src/main/java/org/zaproxy/zap/tasks/internal/Utils.java b/buildSrc/src/main/java/org/zaproxy/zap/tasks/internal/Utils.java index 415be5893e4..ba6971ced78 100644 --- a/buildSrc/src/main/java/org/zaproxy/zap/tasks/internal/Utils.java +++ b/buildSrc/src/main/java/org/zaproxy/zap/tasks/internal/Utils.java @@ -73,8 +73,12 @@ public static MainAddOnsData parseData(Path file) throws IOException { } public static String hash(Path file, MainAddOn addOn) throws IOException { - String hash = addOn.getHash(); - String algorithm = hash.substring(0, hash.indexOf(':')); + String existingHash = addOn.getHash(); + String algorithm = existingHash.substring(0, existingHash.indexOf(':')); + return algorithm + ":" + digest(file, algorithm); + } + + public static String digest(Path file, String algorithm) throws IOException { try (InputStream is = new BufferedInputStream(Files.newInputStream(file))) { MessageDigest diggest = MessageDigest.getInstance(algorithm); @@ -84,8 +88,7 @@ public static String hash(Path file, MainAddOn addOn) throws IOException { diggest.update(buffer, 0, read); } - StringBuilder sb = new StringBuilder(algorithm); - sb.append(':'); + StringBuilder sb = new StringBuilder(); for (byte b : diggest.digest()) { sb.append(String.format("%02x", b)); } diff --git a/zap/src/main/java/org/apache/commons/httpclient/URI.java b/zap/src/main/java/org/apache/commons/httpclient/URI.java index f8363194f61..b6084c0b544 100644 --- a/zap/src/main/java/org/apache/commons/httpclient/URI.java +++ b/zap/src/main/java/org/apache/commons/httpclient/URI.java @@ -57,6 +57,7 @@ * - Use neutral Locale when converting to lower case. * - Allow to create a URI from the authority component. * - Replace usages of StringBuffer with StringBuilder. + * - Include URL encode/decode logic from Apache Commons Codec URLCodec (see encodeUrl/decodeUrl). */ /** * The interface for the URI(Uniform Resource Identifiers) version of RFC 2396. @@ -1702,19 +1703,70 @@ public URI(URI base, URI relative) throws URIException { * @return URI character sequence * @throws URIException null component or unsupported character encoding */ + protected static char[] encode(String original, BitSet allowed, + String charset) throws URIException { + if (original == null) { + throw new IllegalArgumentException("Original string may not be null"); + } + if (allowed == null) { + throw new IllegalArgumentException("Allowed bitset may not be null"); + } + byte[] rawdata = encodeUrl(allowed, EncodingUtil.getBytes(original, charset)); + return EncodingUtil.getAsciiString(rawdata).toCharArray(); + } + + /* + * The following encodeUrl and decodeUrl methods are from Apache Commons Codec + * org.apache.commons.codec.net.URLCodec, licensed under the Apache License, Version 2.0. + * See https://commons.apache.org/proper/commons-codec/ + * Adapted to throw URIException instead of DecoderException. + */ + private static final byte ESCAPE_CHAR = '%'; + + private static final BitSet WWW_FORM_URL_SAFE; + static { + BitSet safe = new BitSet(256); + for (int i = 'a'; i <= 'z'; i++) { + safe.set(i); + } + for (int i = 'A'; i <= 'Z'; i++) { + safe.set(i); + } + for (int i = '0'; i <= '9'; i++) { + safe.set(i); + } + safe.set('-'); + safe.set('_'); + safe.set('.'); + safe.set('*'); + safe.set(' '); + WWW_FORM_URL_SAFE = safe; + } + private static byte[] encodeUrl(BitSet urlsafe, byte[] bytes) { if (bytes == null) { return null; } - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - for (byte c : bytes) { - int b = c & 0xff; + if (urlsafe == null) { + urlsafe = WWW_FORM_URL_SAFE; + } + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + for (final byte c : bytes) { + int b = c; + if (b < 0) { + b = 256 + b; + } if (urlsafe.get(b)) { - buffer.write(b == ' ' ? '+' : b); + if (b == ' ') { + b = '+'; + } + buffer.write(b); } else { - buffer.write('%'); - buffer.write(Character.toUpperCase(Character.forDigit(b >> 4, 16))); - buffer.write(Character.toUpperCase(Character.forDigit(b & 0x0f, 16))); + buffer.write(ESCAPE_CHAR); + final char hex1 = hexChar(b >> 4); + final char hex2 = hexChar(b); + buffer.write(hex1); + buffer.write(hex2); } } return buffer.toByteArray(); @@ -1724,21 +1776,19 @@ private static byte[] decodeUrl(byte[] bytes) throws URIException { if (bytes == null) { return null; } - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); for (int i = 0; i < bytes.length; i++) { - int b = bytes[i] & 0xff; + final int b = bytes[i]; if (b == '+') { buffer.write(' '); - } else if (b == '%') { - if (i + 2 >= bytes.length) { - throw new URIException("Invalid URL encoding: incomplete trailing escape"); - } - int u = Character.digit((char) bytes[++i], 16); - int l = Character.digit((char) bytes[++i], 16); - if (u < 0 || l < 0) { - throw new URIException("Invalid URL encoding: invalid hex digit"); + } else if (b == ESCAPE_CHAR) { + try { + final int u = digit16(bytes[++i]); + final int l = digit16(bytes[++i]); + buffer.write((char) ((u << 4) + l)); + } catch (final ArrayIndexOutOfBoundsException e) { + throw new URIException("Invalid URL encoding: " + e.getMessage()); } - buffer.write((u << 4) + l); } else { buffer.write(b); } @@ -1746,16 +1796,16 @@ private static byte[] decodeUrl(byte[] bytes) throws URIException { return buffer.toByteArray(); } - protected static char[] encode(String original, BitSet allowed, - String charset) throws URIException { - if (original == null) { - throw new IllegalArgumentException("Original string may not be null"); - } - if (allowed == null) { - throw new IllegalArgumentException("Allowed bitset may not be null"); + private static int digit16(byte b) throws URIException { + final int i = Character.digit((char) b, 16); + if (i == -1) { + throw new URIException("Invalid URL encoding: not a valid digit (radix 16): " + b); } - byte[] rawdata = encodeUrl(allowed, EncodingUtil.getBytes(original, charset)); - return EncodingUtil.getAsciiString(rawdata).toCharArray(); + return i; + } + + private static char hexChar(int b) { + return Character.toUpperCase(Character.forDigit(b & 0xF, 16)); } /** diff --git a/zap/zap.gradle.kts b/zap/zap.gradle.kts index 54aaf3a61b7..ea7cc14b449 100644 --- a/zap/zap.gradle.kts +++ b/zap/zap.gradle.kts @@ -72,12 +72,8 @@ spotless { mapOf( "import org.apache.commons.lang." to "Import/use classes from Commons Lang 3, instead of Lang 2.", - "import org.apache.commons.codec.binary.Base64" to - "Use java.util.Base64 instead.", - "import org.apache.commons.codec.binary.Hex" to - "Use java.util.HexFormat instead.", - "import org.apache.commons.codec.digest.DigestUtils" to - "Use org.zaproxy.zap.utils.DigestUtils instead.", + "import org.apache.commons.codec." to + "Do not use import org.apache.commons.codec.", ), ), ) @@ -96,6 +92,7 @@ dependencies { api("com.fifesoft:rsyntaxtextarea:3.6.0") api("com.github.zafarkhaja:java-semver:0.10.2") implementation("commons-beanutils:commons-beanutils:1.11.0") + implementation("commons-codec:commons-codec:1.20.0") api("commons-collections:commons-collections:3.2.2") api("commons-configuration:commons-configuration:1.10") api("commons-httpclient:commons-httpclient:3.1")