英文:
Spring Thymeleaf 3.1 post processor
问题
org.thymeleaf.standard.processor.StandardTextTagProcessor#produceEscapedOutput
方法在XML模板情况下对th:text
执行了XmlEscape.escapeXml10
。我不希望它被转义,我知道可以使用th:utext
来获取未转义的文本,但是我有太多分散在各处的模板需要找到并修复。
我的想法是使用IPostProcessorDialect
在处理器执行转义后对文本进行取消转义,但是我找不到一个好的示例来实现这一点。到目前为止,我已经做了以下工作:
实现了IPostProcessorDialect
,实现了一个自定义的IPostProcessor
,将其返回给IPostProcessorDialect,getPostProcessors()
,扩展了AbstractTemplateHandler
并重写了handleText
方法,但这对我来说是死胡同。你能建议如何处理这个问题吗?
英文:
I have an issue related to Thymeleaf 3.1,
org.thymeleaf.standard.processor.StandardTextTagProcessor#produceEscapedOutput
method performs XmlEscape.escapeXml10
on th:text
in case of XML template.
I do not want it to be escaped, I know there is an option to use th:utext
for unescaped text but I have too many templates scattered over places to find and fix them all.
My idea was to use the IPostProcessorDialect
to unescape the text after the processor has performed the escape, but I can not find a good example of how to achieve this.
So far this is what I have done.
Implemented IPostProcessorDialect
, implemented a custom IPostProcessor
to be returned by IPostProcessorDialect,getPostProcessors()
extended AbstractTemplateHandler
and overridden the handleText
method, this is the dead end for me. Can you suggest how to handle this.
答案1
得分: 1
Introduction
您绝对在正确的轨道上实现您想要的功能。请注意,通常在XML文档中使用原始的、未转义的文本是一个坏主意,特别是如果注入到XML中的内容是由用户控制的。您可能会面临实体注入、XSS和其他与XML相关的漏洞的风险,以及潜在的XML破损问题。
在解决了这个问题之后,让我尝试回答您的下一步可能是什么,希望不会太晚。
实现和连接后处理器
正如您已经了解到的,Thymeleaf支持后处理器。这些后处理器是对XML的访问者,您可以实现它们。
实际后处理器
这是通过创建ITemplateHandler的实现来完成的,最简单的方法是扩展AbstractTemplateHandler。
这是我的后处理器的示例,它只是获取所有XML节点中的所有文本,并进行反转义。根据节点名称等,应根据您的需求进行修改。
TemplateHandler将在XML处理中是唯一的,因此您可以存储实例变量以了解您的位置并保持状态。
在这里,我重写了handleText
方法,检索实际文本,反转义它,然后使用为Thymeleaf配置的模型工厂创建一个新的IText节点(因为参数传入的节点是不可变的),然后将新节点发送到AbstractTemplateHandler中的标准处理程序。
import org.thymeleaf.engine.AbstractTemplateHandler;
import org.thymeleaf.engine.ITemplateHandler;
import org.thymeleaf.model.IText;
import org.unbescape.xml.XmlEscape;
public class XmlTemplateHandler extends AbstractTemplateHandler implements ITemplateHandler {
@Override
public void handleText(IText textNode) {
String text = textNode.getText();
String unescaped = XmlEscape.unescapeXml(text);
IText newNode = getContext().getModelFactory().createText(unescaped);
super.handleText(newNode);
}
}
连接后处理器
要让Thymeleaf知道后处理器,您需要进行连接。这是通过实现IPostProcessorDialect来完成的,该方言将返回Thymeleaf应用于XML后处理的后处理器列表。以下是我粗略示例的一个示例后处理器方言。
import org.springframework.stereotype.Component;
import org.thymeleaf.dialect.AbstractDialect;
import org.thymeleaf.dialect.IPostProcessorDialect;
import org.thymeleaf.postprocessor.IPostProcessor;
import org.thymeleaf.postprocessor.PostProcessor;
import org.thymeleaf.templatemode.TemplateMode;
import java.util.Set;
@Component
public class XmlPostProcessorDialect extends AbstractDialect implements IPostProcessorDialect {
protected XmlPostProcessorDialect() {
super("xmlPostProcessor");
}
@Override
public int getDialectPostProcessorPrecedence() {
return 0;
}
@Override
public Set<IPostProcessor> getPostProcessors() {
return Set.of(new PostProcessor(TemplateMode.XML, XmlTemplateHandler.class, 0));
}
}
现在,您需要确保Thymeleaf知道这个方言。如果您使用带有spring-boot-starter-thymeleaf的Spring Boot,Spring Boot自动配置将自动完成这个连接。您可以在org.springframework.boot.autoconfigure.thymeleaf.TemplateEngineConfigurations的源代码中看到这一点,实际上连接所有方言的行是dialects.orderedStream().forEach(engine::addDialect);
@Bean
@ConditionalOnMissingBean(ISpringTemplateEngine.class)
SpringTemplateEngine templateEngine(ThymeleafProperties properties,
ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
dialects.orderedStream().forEach(engine::addDialect);
return engine;
}
注意事项
只有在您没有实现/定义自己的ISpringTemplateEngine并且ThymeleafAutoConfiguration已被激活时,才会启动此bean。
如果您不使用Spring Boot自动配置,您需要在初始化模板引擎的地方自己连接方言,使用与ThymeleafAutoConfiguration相同的模式dialects.orderedStream().forEach(engine::addDialect);
。
代码示例
对于基于Gradle的Spring Boot应用程序,使用thymeleaf starter来实现您想要的功能,请参阅https://github.com/fjank/thleaf。
最后的话
当然,如果您对此有任何问题,我将尽力协助您找到答案。Thymeleaf是一个很好的框架,并且与Spring Boot非常搭配,但它们都是庞大而复杂的东西,有时候它们是如何连接在一起的可能会令人困惑。
英文:
Introduction
You are definitly on the right track to do what you want. Do note that generally it is a bad idea to use raw, unescaped text in an XML document, especially if the content that is injected into the XML is user controlled. You may risk Entity injection, XSS and other XML related vulnerabilities, in addition to a potential broken XML.
With that out of the way, let me try to give an answer on what your next steps could be, hopefully this is not too late.
Implementing and wiring up the post-processor
As you have figured out, Thymeleaf has support for post-processors. These are visitors to an XML that you can implement.
Actual post-processor
This is implemented by creating an implementation of ITemplateHandler, the easiest way is to extend AbstractTemplateHandler.
This is an example for my postprocessor that simply takes all text in all XML nodes, and unescapes. Should obviously be retrofitted to your needs, based on e.g. node name etc.
The TemplateHandler will be unique pr XML processing, so you can store instance variables to know where you are and to keep state.
Here I have overriden the handleText
method, retrieve the actual text, unescape it, then I create a new IText node (since the nodes coming in as parameters are immutable) with the aid of the modelfactory that is configured for thymeleaf, and send the new node to the standard handler in AbstractTemplateHandler.
import org.thymeleaf.engine.AbstractTemplateHandler;
import org.thymeleaf.engine.ITemplateHandler;
import org.thymeleaf.model.IText;
import org.unbescape.xml.XmlEscape;
public class XmlTemplateHandler extends AbstractTemplateHandler implements ITemplateHandler {
@Override
public void handleText(IText textNode) {
String text = textNode.getText();
String unescaped = XmlEscape.unescapeXml(text);
IText newNode = getContext().getModelFactory().createText(unescaped);
super.handleText(newNode);
}
}
Wiring up the post-processor
To make thymeleaf aware of the post-processor, you need to wire it up. This is done by implementing a IPostProcessorDialect, that will return a list of postprocessors that thymeleaf should apply for XML post-processing. Below is my crude example of such a postprocessordialect.
import org.springframework.stereotype.Component;
import org.thymeleaf.dialect.AbstractDialect;
import org.thymeleaf.dialect.IPostProcessorDialect;
import org.thymeleaf.postprocessor.IPostProcessor;
import org.thymeleaf.postprocessor.PostProcessor;
import org.thymeleaf.templatemode.TemplateMode;
import java.util.Set;
@Component
public class XmlPostProcessorDialect extends AbstractDialect implements IPostProcessorDialect {
protected XmlPostProcessorDialect() {
super("xmlPostProcessor");
}
@Override
public int getDialectPostProcessorPrecedence() {
return 0;
}
@Override
public Set<IPostProcessor> getPostProcessors() {
return Set.of(new PostProcessor(TemplateMode.XML, XmlTemplateHandler.class, 0));
}
}
Now, you need to make sure Thymeleaf is aware of this dialect. If you use spring boot with spring-boot-starter-thymeleaf you will get this wiring done automatically by spring boot autoconfiguration. You can see this in the sourcecode for org.springframework.boot.autoconfigure.thymeleaf.TemplateEngineConfigurations, the line actually wiring up all the dialects is dialects.orderedStream().forEach(engine::addDialect);
@Bean
@ConditionalOnMissingBean(ISpringTemplateEngine.class)
SpringTemplateEngine templateEngine(ThymeleafProperties properties,
ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
dialects.orderedStream().forEach(engine::addDialect);
return engine;
}
Gotchas
This bean is only started up if you do not implement/defined your own ISpringTemplateEngine and that ThymeleafAutoConfiguration as been activated.
If you do not use Spring Boot Autoconfiguration, you need to wire up the dialects yourself in the place where you initiate the template engine, using the same pattern as ThymeleafAutoConfiguration with dialects.orderedStream().forEach(engine::addDialect);
.
Code samples
For a gradle based Spring Boot application using thymeleaf starter, implementing your wanted functionality, take a look at https://github.com/fjank/thleaf
Final remarks
Of course, if you have any questions regarding this, I will do my best to assist you figuring out the answers. Thymeleaf is a good framework, and fits quite nice together with Spring Boot, but they are enormous, complex beasts, and it can sometimes be quite confusing how things are hooked together.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论