This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts-intellij-plugin.git
The following commit(s) were added to refs/heads/main by this push:
new 53d382f feat(inspection): add early DTD validation for Struts config
files (#66)
53d382f is described below
commit 53d382f7dce31a72763a8f6e2a1bebad8cfda37f
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sun Apr 5 12:44:01 2026 +0200
feat(inspection): add early DTD validation for Struts config files (#66)
Surface invalid DOCTYPE SYSTEM URIs (http:// instead of https://, or
unrecognized URIs) as a file-level warning in Struts2ModelInspection,
so users discover the issue while editing struts.xml rather than only
when opening the Diagram tab.
- Extract StrutsDtdValidator as a shared helper for DTD URI checks
- Call the validator from Struts2ModelInspection.checkFileElement()
- Add message keys for both http-vs-https and unrecognized DTD warnings
- Add StrutsDtdValidatorTest (unit) and highlighting regression test
Made-with: Cursor
---
.../dom/inspection/Struts2ModelInspection.java | 22 +++++++
.../struts2/dom/inspection/StrutsDtdValidator.java | 72 +++++++++++++++++++++
.../resources/messages/Struts2Bundle.properties | 2 +
.../dom/inspection/StrutsDtdValidatorTest.java | 74 ++++++++++++++++++++++
.../struts2/dom/struts/StrutsHighlightingTest.java | 4 ++
.../strutsXml/highlighting/struts-dtd-https.xml | 13 ++++
6 files changed, 187 insertions(+)
diff --git
a/src/main/java/com/intellij/struts2/dom/inspection/Struts2ModelInspection.java
b/src/main/java/com/intellij/struts2/dom/inspection/Struts2ModelInspection.java
index d92a78d..b7f8dd0 100644
---
a/src/main/java/com/intellij/struts2/dom/inspection/Struts2ModelInspection.java
+++
b/src/main/java/com/intellij/struts2/dom/inspection/Struts2ModelInspection.java
@@ -16,6 +16,7 @@
package com.intellij.struts2.dom.inspection;
import com.intellij.codeInspection.options.OptPane;
+import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.text.StringUtil;
@@ -95,12 +96,33 @@ public class Struts2ModelInspection extends
BasicDomElementsInspection<StrutsRoo
for (final StrutsFileSet strutsFileSet : fileSets) {
LOG.info("Checking file set: " + strutsFileSet);
if (strutsFileSet.hasFile(virtualFile)) {
+ checkDtdUri(xmlFile, strutsRootDomFileElement.getRootElement(),
holder);
super.checkFileElement(strutsRootDomFileElement, holder);
break;
}
}
}
+ private static void checkDtdUri(@NotNull XmlFile xmlFile,
+ @NotNull StrutsRoot strutsRoot,
+ @NotNull DomElementAnnotationHolder holder)
{
+ StrutsDtdValidator.Result result = StrutsDtdValidator.validate(xmlFile);
+ switch (result) {
+ case HTTP_INSTEAD_OF_HTTPS -> {
+ String systemId = StrutsDtdValidator.extractSystemId(xmlFile);
+ holder.createProblem(strutsRoot, HighlightSeverity.WARNING,
+
StrutsBundle.message("inspections.struts2.model.dtd.http.instead.of.https",
+ StrutsDtdValidator.suggestedUri(systemId)));
+ }
+ case UNRECOGNIZED -> {
+ String systemId = StrutsDtdValidator.extractSystemId(xmlFile);
+ holder.createProblem(strutsRoot, HighlightSeverity.WARNING,
+ StrutsBundle.message("inspections.struts2.model.dtd.unrecognized",
systemId));
+ }
+ default -> { }
+ }
+ }
+
@Override
protected boolean shouldCheckResolveProblems(final GenericDomValue value) {
final Converter converter = value.getConverter();
diff --git
a/src/main/java/com/intellij/struts2/dom/inspection/StrutsDtdValidator.java
b/src/main/java/com/intellij/struts2/dom/inspection/StrutsDtdValidator.java
new file mode 100644
index 0000000..25601cf
--- /dev/null
+++ b/src/main/java/com/intellij/struts2/dom/inspection/StrutsDtdValidator.java
@@ -0,0 +1,72 @@
+/*
+ * 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
+ *
+ * http://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 com.intellij.struts2.dom.inspection;
+
+import com.intellij.psi.xml.XmlDocument;
+import com.intellij.psi.xml.XmlFile;
+import com.intellij.psi.xml.XmlProlog;
+import com.intellij.struts2.StrutsConstants;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Validates the DOCTYPE SYSTEM URI of a Struts configuration file against the
+ * known DTD URIs registered in {@link StrutsConstants#STRUTS_DTDS}.
+ * <p>
+ * Shared between the DOM model inspection and the Diagram file editor so the
+ * validation rule is defined in one place.
+ */
+public final class StrutsDtdValidator {
+
+ private StrutsDtdValidator() {}
+
+ public enum Result {
+ OK,
+ HTTP_INSTEAD_OF_HTTPS,
+ UNRECOGNIZED
+ }
+
+ public static @NotNull Result validate(@NotNull XmlFile xmlFile) {
+ String systemId = extractSystemId(xmlFile);
+ if (systemId == null) return Result.OK;
+
+ for (String dtd : StrutsConstants.STRUTS_DTDS) {
+ if (dtd.equals(systemId)) return Result.OK;
+ }
+
+ if (systemId.startsWith("http://struts.apache.org/dtds/struts-")) {
+ return Result.HTTP_INSTEAD_OF_HTTPS;
+ }
+
+ return Result.UNRECOGNIZED;
+ }
+
+ public static @Nullable String extractSystemId(@NotNull XmlFile xmlFile) {
+ XmlDocument document = xmlFile.getDocument();
+ if (document == null) return null;
+ XmlProlog prolog = document.getProlog();
+ if (prolog == null || prolog.getDoctype() == null) return null;
+
+ String systemId = prolog.getDoctype().getDtdUri();
+ if (systemId == null || systemId.isEmpty()) return null;
+ return systemId;
+ }
+
+ public static @NotNull String suggestedUri(@NotNull String httpUri) {
+ return httpUri.replace("http://", "https://");
+ }
+}
diff --git a/src/main/resources/messages/Struts2Bundle.properties
b/src/main/resources/messages/Struts2Bundle.properties
index 075b419..3dc6c4f 100644
--- a/src/main/resources/messages/Struts2Bundle.properties
+++ b/src/main/resources/messages/Struts2Bundle.properties
@@ -77,4 +77,6 @@ facet.fileset.editor.label.fileset.name=File Set &name:
create.config.new.file=Struts Config
create.config.new.file.description=Create new Struts Config file
action.AnActionButton.text.open.struts.2.plugin.documentation=Open Struts 2
Plugin Documentation
+inspections.struts2.model.dtd.http.instead.of.https=DOCTYPE uses http:// but
the Struts DTD requires https://. Change the SYSTEM identifier to: {0}
+inspections.struts2.model.dtd.unrecognized=Unrecognized DOCTYPE SYSTEM URI:
{0}. The plugin may not resolve Struts configuration correctly.
notification.group.struts2=Struts 2
diff --git
a/src/test/java/com/intellij/struts2/dom/inspection/StrutsDtdValidatorTest.java
b/src/test/java/com/intellij/struts2/dom/inspection/StrutsDtdValidatorTest.java
new file mode 100644
index 0000000..6b9921d
--- /dev/null
+++
b/src/test/java/com/intellij/struts2/dom/inspection/StrutsDtdValidatorTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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
+ *
+ * http://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 com.intellij.struts2.dom.inspection;
+
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.xml.XmlFile;
+import com.intellij.testFramework.fixtures.BasePlatformTestCase;
+
+public class StrutsDtdValidatorTest extends BasePlatformTestCase {
+
+ public void testHttpUriDetected() {
+ XmlFile file =
createStrutsXmlWithDoctype("http://struts.apache.org/dtds/struts-6.0.dtd");
+ assertEquals(StrutsDtdValidator.Result.HTTP_INSTEAD_OF_HTTPS,
StrutsDtdValidator.validate(file));
+ }
+
+ public void testHttpsUriOk() {
+ XmlFile file =
createStrutsXmlWithDoctype("https://struts.apache.org/dtds/struts-6.0.dtd");
+ assertEquals(StrutsDtdValidator.Result.OK,
StrutsDtdValidator.validate(file));
+ }
+
+ public void testOldHttpUriOk() {
+ XmlFile file =
createStrutsXmlWithDoctype("http://struts.apache.org/dtds/struts-2.0.dtd");
+ assertEquals(StrutsDtdValidator.Result.OK,
StrutsDtdValidator.validate(file));
+ }
+
+ public void testUnrecognizedUri() {
+ XmlFile file =
createStrutsXmlWithDoctype("http://example.com/bogus.dtd");
+ assertEquals(StrutsDtdValidator.Result.UNRECOGNIZED,
StrutsDtdValidator.validate(file));
+ }
+
+ public void testNoDoctype() {
+ PsiFile psiFile = myFixture.configureByText("struts.xml",
"<struts></struts>");
+ assertEquals(StrutsDtdValidator.Result.OK,
StrutsDtdValidator.validate((XmlFile) psiFile));
+ }
+
+ public void testSuggestedUri() {
+ assertEquals("https://struts.apache.org/dtds/struts-6.0.dtd",
+
StrutsDtdValidator.suggestedUri("http://struts.apache.org/dtds/struts-6.0.dtd"));
+ }
+
+ public void testHttp25UriDetected() {
+ XmlFile file =
createStrutsXmlWithDoctype("http://struts.apache.org/dtds/struts-2.5.dtd");
+ assertEquals(StrutsDtdValidator.Result.HTTP_INSTEAD_OF_HTTPS,
StrutsDtdValidator.validate(file));
+ }
+
+ public void testHttps25UriOk() {
+ XmlFile file =
createStrutsXmlWithDoctype("https://struts.apache.org/dtds/struts-2.5.dtd");
+ assertEquals(StrutsDtdValidator.Result.OK,
StrutsDtdValidator.validate(file));
+ }
+
+ private XmlFile createStrutsXmlWithDoctype(String systemUri) {
+ String content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<!DOCTYPE struts PUBLIC\n" +
+ " \"-//Apache Software Foundation//DTD Struts Configuration
6.0//EN\"\n" +
+ " \"" + systemUri + "\">\n" +
+ "<struts></struts>";
+ PsiFile psiFile = myFixture.configureByText("struts.xml", content);
+ return (XmlFile) psiFile;
+ }
+}
diff --git
a/src/test/java/com/intellij/struts2/dom/struts/StrutsHighlightingTest.java
b/src/test/java/com/intellij/struts2/dom/struts/StrutsHighlightingTest.java
index bba472b..1c15bbd 100644
--- a/src/test/java/com/intellij/struts2/dom/struts/StrutsHighlightingTest.java
+++ b/src/test/java/com/intellij/struts2/dom/struts/StrutsHighlightingTest.java
@@ -81,4 +81,8 @@ public class StrutsHighlightingTest extends
StrutsLightHighlightingTestCase {
createStrutsFileSet("struts-default.xml");
myFixture.testHighlighting(false, false, false, "struts-errors.xml");
}
+
+ public void testDtdHttpsNoWarning() {
+ performHighlightingTest("struts-dtd-https.xml");
+ }
}
\ No newline at end of file
diff --git a/src/test/testData/strutsXml/highlighting/struts-dtd-https.xml
b/src/test/testData/strutsXml/highlighting/struts-dtd-https.xml
new file mode 100644
index 0000000..f475703
--- /dev/null
+++ b/src/test/testData/strutsXml/highlighting/struts-dtd-https.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE struts PUBLIC
+ "-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
+ "https://struts.apache.org/dtds/struts-6.0.dtd">
+
+<struts>
+
+ <package name="test" namespace="/test">
+ <action name="index"/>
+ </package>
+
+</struts>