如何禁用 JLabel 的自动 HTML 支持?
Posted
技术标签:
【中文标题】如何禁用 JLabel 的自动 HTML 支持?【英文标题】:How to disable the automatic HTML support of JLabel? 【发布时间】:2011-04-04 19:09:09 【问题描述】:Swing JLabel 自动将任何以 开头的文本解释为 html 内容。如果这个 HTML 的内容是一个带有无效 URL 的图像,这将导致整个 GUI 挂起,因为应该加载这个图像的 ImageFetche 将被 NPE 退出。
要重现此问题,只需按如下方式创建 JLabel
new JLabel("<html><img src='http:\\\\invalid\\url'>")
我知道有一个客户端属性可以防止 JLabel 解释 HTML。但是 JLabel 是许多 Swing 组件(如 JTree、JTable 等)的默认渲染器实现,这对于几乎所有允许用户输入的 Swing 应用程序来说都是一个问题。因此,我没有实现大量的自定义渲染器,而是在寻找一种禁用 HTML 解释的全局解决方案。
【问题讨论】:
是什么阻止了putClientProperty
解决方案的工作?
putClientProperty 确实有效 - 但有许多 JLabel 可能不知道。例如:创建一个 List 作为 new JList( new String[]"entry" )。 “条目”将由 JLabel 呈现(参见 javax.swing.DefaultListCellRenderer)。我不知道任何通用的解决方案来阻止像这样的 JLabels 解释 HTML。如果你用上面提到的 HTML 替换 'entry',GUI 将会挂起。
【参考方案1】:
由于无法为每个创建的 JLabel
全局设置 html.disable
属性为 true,因此这是一种 hacky 方式(我说 hacky 是因为我不确定对性能的影响,或者这样的解决方案是否可以投入生产)是为每个创建的JLabel
实例做一些字节码拦截。像 ByteBuddy 这样的库可以做到这一点。我已经对 ByteBuddy 进行了一些试验,并找到了一种设置 Java agent 的方法,该方法可以拦截对 setText()
方法的调用以获取 JLabel
。使用提供的文本创建 JLabel
时会调用此方法。
代理
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.AgentBuilder.InitializationStrategy;
import net.bytebuddy.agent.builder.AgentBuilder.Listener;
import net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy;
import net.bytebuddy.agent.builder.AgentBuilder.TypeStrategy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.matcher.StringMatcher;
import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.nio.file.Files;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.description.type.TypeDescription.ForLoadedType;
import static net.bytebuddy.dynamic.ClassFileLocator.ForClassLoader.read;
import static net.bytebuddy.dynamic.loading.ClassInjector.UsingInstrumentation.Target.BOOTSTRAP;
import static net.bytebuddy.matcher.ElementMatchers.*;
public class JLabelAgent
private static final Class<?> INTERCEPTOR_CLASS = JLabelInterceptor.class;
private JLabelAgent()
public static void premain(String arg, Instrumentation instrumentation) throws Exception
injectBootstrapClasses(instrumentation);
new AgentBuilder.Default()
.with(RedefinitionStrategy.RETRANSFORMATION)
.with(InitializationStrategy.NoOp.INSTANCE)
.with(TypeStrategy.Default.REDEFINE)
.ignore(new AgentBuilder.RawMatcher.ForElementMatchers(nameStartsWith("net.bytebuddy.").or(isSynthetic()), any(), any()))
.with(new Listener.Filtering(
new StringMatcher("javax.swing.JLabel", StringMatcher.Mode.EQUALS_FULLY),
Listener.StreamWriting.toSystemOut()))
.type(named("javax.swing.JLabel"))
.transform((builder, type, classLoader, module) ->
builder.visit(Advice.to(INTERCEPTOR_CLASS).on(named("setText")))
)
.installOn(instrumentation);
private static void injectBootstrapClasses(Instrumentation instrumentation) throws IOException
File temp = Files.createTempDirectory("tmp").toFile();
temp.deleteOnExit();
ClassInjector.UsingInstrumentation.of(temp, BOOTSTRAP, instrumentation)
.inject(singletonMap(new ForLoadedType(INTERCEPTOR_CLASS), read(INTERCEPTOR_CLASS)));
拦截器
import javax.swing.JComponent;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.Argument;
import net.bytebuddy.asm.Advice.This;
public class JLabelInterceptor
@Advice.OnMethodEnter()
public static void setText(@This Object label, @Argument(0) String text)
((JComponent) label).putClientProperty("html.disable", Boolean.TRUE);
System.out.println("Label text is " + text);
示例
public static void main(String[] args) throws Exception
JFrame frame = new JFrame("JList Test");
frame.setLayout(new FlowLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String[] selections = "<html><img src='http:\\\\invalid\\url'>", "<html><H1>Hello</h1></html>", "orange", "dark blue";
JList list = new JList(selections);
list.setSelectedIndex(1);
System.out.println(list.getSelectedValue());
JLabel jLabel = new JLabel("<html><h2>standard Label</h2></html>");
frame.add(new JScrollPane(list));
frame.add(jLabel);
frame.pack();
frame.setVisible(true);
运行示例
编译 Java 代理,然后运行示例:
java -javaagent:agent.jar -jar example.jar
注意:在使用 Maven 构建代理 Jar 时,我必须在 POM 中放入以下配置来设置清单:
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Agent-Class>example.JLabelAgent</Agent-Class>
<Premain-Class>example.JLabelAgent</Premain-Class>
<Boot-Class-Path>byte-buddy-1.10.14.jar</Boot-Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
【讨论】:
【参考方案2】:如果您创建自己的外观和感觉,有一种方法。 我不确定它的性能如何,但它确实有效。假设您将扩展“经典 Windows”L&F。您至少需要 2 个类 一个是 Look&Feel 本身,我们称之为 WindowsClassicLookAndFeelExt。您只需要重写方法 initClassDefaults。
package testSwing;
import javax.swing.UIDefaults;
import com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel;
public class WindowsClassicLookAndFeelExt extends WindowsClassicLookAndFeel
@Override protected void initClassDefaults(UIDefaults table)
super.initClassDefaults(table);
Object[] uiDefaults = "LabelUI", WindowsLabelExtUI.class.getCanonicalName();
table.putDefaults(uiDefaults);
您还需要一个 WindowsLabelExtUI 类来管理所有 JLabel 并设置属性:
package testSwing;
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
import com.sun.java.swing.plaf.windows.WindowsLabelUI;
public class WindowsLabelExtUI extends WindowsLabelUI
static WindowsLabelExtUI singleton = new WindowsLabelExtUI();
public static ComponentUI createUI(JComponent c)
c.putClientProperty("html.disable", Boolean.TRUE);
return singleton;
当您将主题设置为 WindowsClassicLookAndFeelExt 时,最后是一个测试类
package testSwing;
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
public class Main
public static void main(String[] args)
try UIManager.setLookAndFeel(WindowsClassicLookAndFeelExt.class.getCanonicalName());
catch (Exception e)
e.printStackTrace();
JFrame frame = new JFrame("JList Test");
frame.setLayout(new FlowLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String[] selections = "<html><img src='http:\\\\invalid\\url'>", "<html><H1>Hello</h1></html>", "orange", "dark blue";
JList list = new JList(selections);
list.setSelectedIndex(1);
System.out.println(list.getSelectedValue());
JLabel jLabel = new JLabel("<html><h2>standard Label</h2></html>");
frame.add(new JScrollPane(list));
frame.add(jLabel);
frame.pack();
frame.setVisible(true);
你会看到类似的东西
【讨论】:
【参考方案3】:对于一个简单的JLabel,可以调用JComponent方法
myLabel.putClientProperty("html.disable", Boolean.TRUE);
在您要禁用 HTML 呈现的标签上。
参考:Impossible to disable HTML Rendering in a JLabel
对于 JTable、JTree 或 JList 之类的东西,您需要创建一个自定义单元格渲染器来设置此属性。这是一个为JList 创建自定义单元格渲染器的示例(从this example 修改)。
import java.awt.Component;
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
public class JListTest
public static void main(String[] args)
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("JList Test");
frame.setLayout(new FlowLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String[] selections = "<html><img src='http:\\\\invalid\\url'>",
"red", "orange", "dark blue" ;
JList list = new JList(selections);
// set the list cell renderer to the custom class defined below
list.setCellRenderer(new MyCellRenderer());
list.setSelectedIndex(1);
System.out.println(list.getSelectedValue());
frame.add(new JScrollPane(list));
frame.pack();
frame.setVisible(true);
class MyCellRenderer extends JLabel implements ListCellRenderer
public MyCellRenderer()
setOpaque(true);
putClientProperty("html.disable", Boolean.TRUE);
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
setText(value.toString());
return this;
我使用ListCellRenderer 文档中的示例代码作为自定义列表单元格渲染器的起点。
当我运行示例时,您可以看到第一个列表条目中的 HTML 被渲染而不是被解释。
【讨论】:
【参考方案4】:挂起可能是最少不愉快的行为。这就是为什么Data Validation 如此重要的原因。只是不允许用户输入类似的内容。
【讨论】:
Swing 组件中 html 的处理委托给 LAF。因此,如果不实现自定义 LAF,则无法全局禁用对 html 标签的支持。而且,绝对没有理由允许任意、未经验证的用户输入。那只是自找麻烦。从本质上讲,每个缓冲区溢出漏洞实际上都归结为某人未能验证输入。 OP 已经展示了粗心/无知的输入如何使系统挂起。但是考虑一下,恶意用户可能会做什么? 我不确定你会在所有领域禁止什么。不允许不等式()在评论字段中会很奇怪。以上是关于如何禁用 JLabel 的自动 HTML 支持?的主要内容,如果未能解决你的问题,请参考以下文章