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

lukaszlenart pushed a commit to branch feat/early-dtd-validation
in repository https://gitbox.apache.org/repos/asf/struts-intellij-plugin.git

commit 00e4127999b64e104df3cac4414a83b2b5fbff71
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sun Apr 5 12:23:21 2026 +0200

    feat(inspection): add early DTD validation for Struts config files
    
    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>

Reply via email to