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

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-bcel.git


The following commit(s) were added to refs/heads/master by this push:
     new 9a08f934 Escape constant pool names in generated HTML (#500)
9a08f934 is described below

commit 9a08f9342126119a135ba29fe3b28b1ecb0e1785
Author: Dexter.k <[email protected]>
AuthorDate: Fri Jun 12 01:32:08 2026 +0000

    Escape constant pool names in generated HTML (#500)
---
 .../java/org/apache/bcel/util/AttributeHTML.java   |  4 +-
 src/main/java/org/apache/bcel/util/Class2HTML.java |  6 +--
 src/main/java/org/apache/bcel/util/CodeHTML.java   |  6 +--
 .../java/org/apache/bcel/util/ConstantHTML.java    | 10 ++--
 src/main/java/org/apache/bcel/util/MethodHTML.java |  2 +-
 .../org/apache/bcel/util/Class2HTMLXSSTest.java    | 59 ++++++++++++++++++++++
 6 files changed, 75 insertions(+), 12 deletions(-)

diff --git a/src/main/java/org/apache/bcel/util/AttributeHTML.java 
b/src/main/java/org/apache/bcel/util/AttributeHTML.java
index e0bafaf2..f87b1b24 100644
--- a/src/main/java/org/apache/bcel/util/AttributeHTML.java
+++ b/src/main/java/org/apache/bcel/util/AttributeHTML.java
@@ -159,7 +159,7 @@ final class AttributeHTML implements Closeable {
                 signature = Utility.signatureToString(signature, false);
                 final int start = var.getStartPC();
                 final int end = start + var.getLength();
-                printWriter.println("<LI>" + 
Class2HTML.referenceType(signature) + "&nbsp;<B>" + var.getName() + "</B> in 
slot %" + var.getIndex()
+                printWriter.println("<LI>" + 
Class2HTML.referenceType(signature) + "&nbsp;<B>" + 
Class2HTML.toHTML(var.getName()) + "</B> in slot %" + var.getIndex()
                         + "<BR>Valid from lines <A HREF=\"" + className + 
"_code.html#code" + methodNumber + "@" + start + "\" TARGET=Code>" + start
                         + "</A> to <A HREF=\"" + className + "_code.html#code" 
+ methodNumber + "@" + end + "\" TARGET=Code>" + end + "</A></LI>");
             });
@@ -173,7 +173,7 @@ final class AttributeHTML implements Closeable {
                 final String access;
                 index = clazz.getInnerNameIndex();
                 if (index > 0) {
-                    name = constantPool.getConstantUtf8(index).getBytes();
+                    name = 
Class2HTML.toHTML(constantPool.getConstantUtf8(index).getBytes());
                 } else {
                     name = "&lt;anonymous&gt;";
                 }
diff --git a/src/main/java/org/apache/bcel/util/Class2HTML.java 
b/src/main/java/org/apache/bcel/util/Class2HTML.java
index 3a427ffc..b0ced24d 100644
--- a/src/main/java/org/apache/bcel/util/Class2HTML.java
+++ b/src/main/java/org/apache/bcel/util/Class2HTML.java
@@ -135,7 +135,7 @@ public class Class2HTML implements Constants {
         String str = constantPool.getConstantString(index, 
Const.CONSTANT_Class);
         str = Utility.compactClassName(str);
         str = Utility.compactClassName(str, classPackage + ".", true);
-        return "<A HREF=\"" + className + "_cp.html#cp" + index + "\" 
TARGET=ConstantPool>" + str + "</A>";
+        return "<A HREF=\"" + className + "_cp.html#cp" + index + "\" 
TARGET=ConstantPool>" + toHTML(str) + "</A>";
     }
 
     static String referenceType(final String type) {
@@ -150,7 +150,7 @@ public class Class2HTML implements Constants {
         if (basicTypes.contains(baseType)) {
             return "<FONT COLOR=\"#00FF00\">" + type + "</FONT>";
         }
-        return "<A HREF=\"" + baseType + ".html\" TARGET=_top>" + shortType + 
"</A>";
+        return "<A HREF=\"" + baseType + ".html\" TARGET=_top>" + 
toHTML(shortType) + "</A>";
     }
 
     static String toHTML(final String str) {
@@ -221,7 +221,7 @@ public class Class2HTML implements Constants {
         try (PrintWriter file = new PrintWriter(dir + className + ".html", 
charset.name())) {
             // @formatter:off
             file.println("<HTML>\n"
-                + "<HEAD><TITLE>Documentation for " + className + 
"</TITLE></HEAD>\n"
+                + "<HEAD><TITLE>Documentation for " + toHTML(className) + 
"</TITLE></HEAD>\n"
                 + "<FRAMESET BORDER=1 cols=\"30%,*\">\n"
                 + "<FRAMESET BORDER=1 rows=\"80%,*\">\n"
                 + "<FRAME NAME=\"ConstantPool\" SRC=\"" + className + 
"_cp.html" + "\"\n"
diff --git a/src/main/java/org/apache/bcel/util/CodeHTML.java 
b/src/main/java/org/apache/bcel/util/CodeHTML.java
index 1f26c91b..53ffb3c9 100644
--- a/src/main/java/org/apache/bcel/util/CodeHTML.java
+++ b/src/main/java/org/apache/bcel/util/CodeHTML.java
@@ -231,10 +231,10 @@ final class CodeHTML {
             index = c1.getNameAndTypeIndex();
             final String fieldName = constantPool.constantToString(index, 
Const.CONSTANT_NameAndType);
             if (name.equals(className)) { // Local field
-                buf.append("<A 
HREF=\"").append(className).append("_methods.html#field").append(fieldName).append("\"
 TARGET=Methods>").append(fieldName)
-                    .append("</A>\n");
+                buf.append("<A 
HREF=\"").append(className).append("_methods.html#field").append(fieldName).append("\"
 TARGET=Methods>")
+                    .append(Class2HTML.toHTML(fieldName)).append("</A>\n");
             } else {
-                
buf.append(constantHtml.referenceConstant(classIndex)).append(".").append(fieldName);
+                
buf.append(constantHtml.referenceConstant(classIndex)).append(".").append(Class2HTML.toHTML(fieldName));
             }
             break;
         /*
diff --git a/src/main/java/org/apache/bcel/util/ConstantHTML.java 
b/src/main/java/org/apache/bcel/util/ConstantHTML.java
index 081baf0a..a85e1cd3 100644
--- a/src/main/java/org/apache/bcel/util/ConstantHTML.java
+++ b/src/main/java/org/apache/bcel/util/ConstantHTML.java
@@ -123,6 +123,7 @@ final class ConstantHTML {
             final String methodClass = 
constantPool.constantToString(classIndex, Const.CONSTANT_Class);
             String shortMethodClass = Utility.compactClassName(methodClass); 
// I.e., remove java.lang.
             shortMethodClass = Utility.compactClassName(shortMethodClass, 
classPackage + ".", true); // Remove class package prefix
+            shortMethodClass = Class2HTML.toHTML(shortMethodClass);
             // Get method signature
             final ConstantNameAndType c2 = constantPool.getConstant(nameIndex, 
Const.CONSTANT_NameAndType, ConstantNameAndType.class);
             final String signature = 
constantPool.constantToString(c2.getSignatureIndex(), Const.CONSTANT_Utf8);
@@ -159,14 +160,16 @@ final class ConstantHTML {
             final String fieldClass = 
constantPool.constantToString(classIndex, Const.CONSTANT_Class);
             String shortFieldClass = Utility.compactClassName(fieldClass); // 
I.e., remove java.lang.
             shortFieldClass = Utility.compactClassName(shortFieldClass, 
classPackage + ".", true); // Remove class package prefix
+            shortFieldClass = Class2HTML.toHTML(shortFieldClass);
             final String fieldName = constantPool.constantToString(nameIndex, 
Const.CONSTANT_NameAndType);
+            final String htmlFieldName = Class2HTML.toHTML(fieldName);
             if (fieldClass.equals(className)) {
-                ref = "<A HREF=\"" + fieldClass + "_methods.html#field" + 
fieldName + "\" TARGET=Methods>" + fieldName + "</A>";
+                ref = "<A HREF=\"" + fieldClass + "_methods.html#field" + 
fieldName + "\" TARGET=Methods>" + htmlFieldName + "</A>";
             } else {
-                ref = "<A HREF=\"" + fieldClass + ".html\" TARGET=_top>" + 
shortFieldClass + "</A>." + fieldName + "\n";
+                ref = "<A HREF=\"" + fieldClass + ".html\" TARGET=_top>" + 
shortFieldClass + "</A>." + htmlFieldName + "\n";
             }
             constantRef[index] = "<A HREF=\"" + className + "_cp.html#cp" + 
classIndex + "\" TARGET=Constants>" + shortFieldClass + "</A>.<A HREF=\""
-                + className + "_cp.html#cp" + index + "\" 
TARGET=ConstantPool>" + fieldName + "</A>";
+                + className + "_cp.html#cp" + index + "\" 
TARGET=ConstantPool>" + htmlFieldName + "</A>";
             printWriter.println("<P><TT>" + ref + "</TT><BR>\n" + "<UL>" + 
"<LI><A HREF=\"#cp" + classIndex + "\">Class(" + classIndex + ")</A><BR>\n"
                 + "<LI><A HREF=\"#cp" + nameIndex + "\">NameAndType(" + 
nameIndex + ")</A></UL>");
             break;
@@ -176,6 +179,7 @@ final class ConstantHTML {
             final String className2 = constantPool.constantToString(index, 
tag); // / -> .
             String shortClassName = Utility.compactClassName(className2); // 
I.e., remove java.lang.
             shortClassName = Utility.compactClassName(shortClassName, 
classPackage + ".", true); // Remove class package prefix
+            shortClassName = Class2HTML.toHTML(shortClassName);
             ref = "<A HREF=\"" + className2 + ".html\" TARGET=_top>" + 
shortClassName + "</A>";
             constantRef[index] = "<A HREF=\"" + className + "_cp.html#cp" + 
index + "\" TARGET=ConstantPool>" + shortClassName + "</A>";
             printWriter.println("<P><TT>" + ref + "</TT><UL>" + "<LI><A 
HREF=\"#cp" + nameIndex + "\">Name index(" + nameIndex + ")</A></UL>\n");
diff --git a/src/main/java/org/apache/bcel/util/MethodHTML.java 
b/src/main/java/org/apache/bcel/util/MethodHTML.java
index ee7d093a..d6c053de 100644
--- a/src/main/java/org/apache/bcel/util/MethodHTML.java
+++ b/src/main/java/org/apache/bcel/util/MethodHTML.java
@@ -78,7 +78,7 @@ final class MethodHTML {
         final Attribute[] attributes;
         access = Utility.replace(access, " ", "&nbsp;");
         printWriter.print("<TR><TD><FONT COLOR=\"#FF0000\">" + access + 
"</FONT></TD>\n<TD>" + Class2HTML.referenceType(type) + "</TD><TD><A 
NAME=\"field"
-            + name + "\">" + name + "</A></TD>");
+            + name + "\">" + Class2HTML.toHTML(name) + "</A></TD>");
         attributes = field.getAttributes();
         // Write them to the Attributes.html file with anchor "<name>[<i>]"
         for (int i = 0; i < attributes.length; i++) {
diff --git a/src/test/java/org/apache/bcel/util/Class2HTMLXSSTest.java 
b/src/test/java/org/apache/bcel/util/Class2HTMLXSSTest.java
new file mode 100644
index 00000000..478060a9
--- /dev/null
+++ b/src/test/java/org/apache/bcel/util/Class2HTMLXSSTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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
+ *
+ *   https://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.bcel.util;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+import org.apache.bcel.Const;
+import org.apache.bcel.classfile.JavaClass;
+import org.apache.bcel.generic.ClassGen;
+import org.apache.bcel.generic.FieldGen;
+import org.apache.bcel.generic.Type;
+import org.junit.jupiter.api.Test;
+
+class Class2HTMLXSSTest {
+
+    /**
+     * A field name in the constant pool is attacker controlled and may 
contain HTML metacharacters; the generated
+     * documentation must escape it in text context rather than emit it raw.
+     */
+    @Test
+    void testFieldNameIsEscaped() throws Exception {
+        final ClassGen cg = new ClassGen("Evil", "java.lang.Object", 
"Evil.java", Const.ACC_PUBLIC, null);
+        cg.addField(new FieldGen(Const.ACC_PUBLIC, Type.INT, 
"x<script>alert(1)</script>", cg.getConstantPool()).getField());
+        final JavaClass jc = cg.getJavaClass();
+
+        final File outputDir = new File("target/test-output/html-xss");
+        if (!outputDir.mkdirs()) {
+            assertTrue(outputDir.isDirectory());
+        }
+        new Class2HTML(jc, outputDir.getAbsolutePath() + File.separator);
+
+        final String methods = new String(Files.readAllBytes(new 
File(outputDir, "Evil_methods.html").toPath()), StandardCharsets.UTF_8);
+        // The field name rendered as link text must be escaped, not emitted 
as a live tag.
+        assertFalse(methods.contains("\">x<script>"), "field name was emitted 
unescaped in text context");
+        assertTrue(methods.contains("&lt;script&gt;"), "expected the field 
name to be HTML-escaped");
+    }
+}

Reply via email to