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 void checkFileElement(final @NotNull DomFileElement 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}. + *

+ * 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", ""); + 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 = "\n" + + "\n" + + ""; + 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 void testStrutsWithErrorsNotInFilesetNoHighlighting() { 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 @@ + + + + + + + + + + +