diff --git a/application/src/main/java/run/halo/app/theme/dialect/GlobalHeadInjectionProcessor.java b/application/src/main/java/run/halo/app/theme/dialect/GlobalHeadInjectionProcessor.java
index a346d42e0..ee741a887 100644
--- a/application/src/main/java/run/halo/app/theme/dialect/GlobalHeadInjectionProcessor.java
+++ b/application/src/main/java/run/halo/app/theme/dialect/GlobalHeadInjectionProcessor.java
@@ -42,6 +42,9 @@ public class GlobalHeadInjectionProcessor extends AbstractElementModelProcessor
@Override
protected void doProcess(ITemplateContext context, IModel model,
IElementModelStructureHandler structureHandler) {
+ if (context.containsVariable(InjectionExcluderProcessor.EXCLUDE_INJECTION_VARIABLE)) {
+ return;
+ }
// note that this is important!!
Object processedAlready = context.getVariable(PROCESS_FLAG);
diff --git a/application/src/main/java/run/halo/app/theme/dialect/HaloProcessorDialect.java b/application/src/main/java/run/halo/app/theme/dialect/HaloProcessorDialect.java
index a355f5ae8..99105931d 100644
--- a/application/src/main/java/run/halo/app/theme/dialect/HaloProcessorDialect.java
+++ b/application/src/main/java/run/halo/app/theme/dialect/HaloProcessorDialect.java
@@ -41,6 +41,7 @@ public class HaloProcessorDialect extends AbstractProcessorDialect
processors.add(new EvaluationContextEnhancer());
processors.add(new CommentElementTagProcessor(dialectPrefix));
processors.add(new CommentEnabledVariableProcessor());
+ processors.add(new InjectionExcluderProcessor());
return processors;
}
diff --git a/application/src/main/java/run/halo/app/theme/dialect/InjectionExcluderProcessor.java b/application/src/main/java/run/halo/app/theme/dialect/InjectionExcluderProcessor.java
new file mode 100644
index 000000000..5fb6c1c7a
--- /dev/null
+++ b/application/src/main/java/run/halo/app/theme/dialect/InjectionExcluderProcessor.java
@@ -0,0 +1,91 @@
+package run.halo.app.theme.dialect;
+
+import java.util.Set;
+import java.util.regex.Pattern;
+import org.springframework.util.Assert;
+import org.thymeleaf.context.ITemplateContext;
+import org.thymeleaf.model.ITemplateEnd;
+import org.thymeleaf.model.ITemplateStart;
+import org.thymeleaf.processor.templateboundaries.AbstractTemplateBoundariesProcessor;
+import org.thymeleaf.processor.templateboundaries.ITemplateBoundariesProcessor;
+import org.thymeleaf.processor.templateboundaries.ITemplateBoundariesStructureHandler;
+import org.thymeleaf.standard.StandardDialect;
+import org.thymeleaf.templatemode.TemplateMode;
+
+/**
+ *
Determine whether the current template being rendered needs to exclude the processor of
+ * code injection. If it needs to be excluded, set a local variable.
+ * Why do you need to set a local variable here instead of directly judging in the processor?
+ * Because the processor will process the fragment, and if you need to exclude the login
+ * .html
template and the login.html is only a fragment, then the exclusion logic will
+ * fail, so here use {@link ITemplateBoundariesProcessor} events are only fired for the
+ * first-level template to solve this problem.
+ *
+ * @author guqing
+ * @since 2.20.0
+ */
+public class InjectionExcluderProcessor extends AbstractTemplateBoundariesProcessor {
+
+ public static final String EXCLUDE_INJECTION_VARIABLE =
+ InjectionExcluderProcessor.class.getName() + ".EXCLUDE_INJECTION";
+
+ private final PageInjectionExcluder injectionExcluder = new PageInjectionExcluder();
+
+ public InjectionExcluderProcessor() {
+ super(TemplateMode.HTML, StandardDialect.PROCESSOR_PRECEDENCE);
+ }
+
+ @Override
+ public void doProcessTemplateStart(ITemplateContext context, ITemplateStart templateStart,
+ ITemplateBoundariesStructureHandler structureHandler) {
+ if (isExcluded(context)) {
+ structureHandler.setLocalVariable(EXCLUDE_INJECTION_VARIABLE, true);
+ }
+ }
+
+ @Override
+ public void doProcessTemplateEnd(ITemplateContext context, ITemplateEnd templateEnd,
+ ITemplateBoundariesStructureHandler structureHandler) {
+ structureHandler.removeLocalVariable(EXCLUDE_INJECTION_VARIABLE);
+ }
+
+ /**
+ * Check if the template will be rendered is excluded injection.
+ *
+ * @param context template context
+ * @return true if the template is excluded, otherwise false
+ */
+ boolean isExcluded(ITemplateContext context) {
+ return injectionExcluder.isExcluded(context.getTemplateData().getTemplate());
+ }
+
+ static class PageInjectionExcluder {
+
+ private final Set exactMatches = Set.of(
+ "login",
+ "signup",
+ "logout"
+ );
+
+ private final Set regexPatterns = Set.of(
+ Pattern.compile("error/.*"),
+ Pattern.compile("challenges/.*"),
+ Pattern.compile("password-reset/.*")
+ );
+
+ public boolean isExcluded(String templateName) {
+ Assert.notNull(templateName, "Template name must not be null");
+ if (exactMatches.contains(templateName)) {
+ return true;
+ }
+
+ for (Pattern pattern : regexPatterns) {
+ if (pattern.matcher(templateName).matches()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/application/src/main/java/run/halo/app/theme/dialect/TemplateFooterElementTagProcessor.java b/application/src/main/java/run/halo/app/theme/dialect/TemplateFooterElementTagProcessor.java
index 26a286b26..7177d2f78 100644
--- a/application/src/main/java/run/halo/app/theme/dialect/TemplateFooterElementTagProcessor.java
+++ b/application/src/main/java/run/halo/app/theme/dialect/TemplateFooterElementTagProcessor.java
@@ -48,6 +48,10 @@ public class TemplateFooterElementTagProcessor extends AbstractElementTagProcess
protected void doProcess(ITemplateContext context, IProcessableElementTag tag,
IElementTagStructureHandler structureHandler) {
+ if (context.containsVariable(InjectionExcluderProcessor.EXCLUDE_INJECTION_VARIABLE)) {
+ return;
+ }
+
IModel modelToInsert = context.getModelFactory().createModel();
/*
* Obtain the Spring application context.
diff --git a/application/src/test/java/run/halo/app/theme/dialect/InjectionExcluderProcessorTest.java b/application/src/test/java/run/halo/app/theme/dialect/InjectionExcluderProcessorTest.java
new file mode 100644
index 000000000..89a767fb1
--- /dev/null
+++ b/application/src/test/java/run/halo/app/theme/dialect/InjectionExcluderProcessorTest.java
@@ -0,0 +1,62 @@
+package run.halo.app.theme.dialect;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link InjectionExcluderProcessor}.
+ *
+ * @author guqing
+ * @since 2.20.0
+ */
+class InjectionExcluderProcessorTest {
+
+ @Nested
+ class PageInjectionExcluderTest {
+ final InjectionExcluderProcessor.PageInjectionExcluder pageInjectionExcluder =
+ new InjectionExcluderProcessor.PageInjectionExcluder();
+
+ @Test
+ void excludeTest() {
+ var cases = new String[] {
+ "login",
+ "signup",
+ "logout",
+ "password-reset/email/reset",
+ "error/404",
+ "error/500",
+ "challenges/totp"
+ };
+
+ for (String templateName : cases) {
+ assertThat(pageInjectionExcluder.isExcluded(templateName)).isTrue();
+ }
+ }
+
+ @Test
+ void shouldNotExcludeTest() {
+ var cases = new String[] {
+ "index",
+ "post",
+ "page",
+ "category",
+ "tag",
+ "archive",
+ "search",
+ "feed",
+ "sitemap",
+ "robots",
+ "custom",
+ "error",
+ "login.html",
+ };
+
+ for (String templateName : cases) {
+ assertThat(pageInjectionExcluder.isExcluded(templateName)).isFalse();
+ }
+ }
+ }
+}
\ No newline at end of file