From b80dafb58ebc4baea88222f1c2e414027f5b6272 Mon Sep 17 00:00:00 2001 From: azerr Date: Fri, 17 Nov 2023 11:17:11 +0100 Subject: [PATCH] Separate instructions for testing well formed XML files and validation against DTD See https://github.com/redhat-developer/vscode-xml/issues/947 Signed-off-by: azerr --- .../eclipse/lemminx/XMLLanguageServer.java | 5 +- .../lemminx/XMLTextDocumentService.java | 17 +++--- .../commands/XMLValidationFileCommand.java | 15 ++++- .../diagnostics/XMLValidator.java | 41 ++++++++++++- .../contentmodel/settings/DTDEnabled.java | 17 ++++++ .../contentmodel/settings/XMLDTDSettings.java | 60 +++++++++++++++++++ .../settings/XMLValidationSettings.java | 30 ++++++++++ .../services/IXMLValidationService.java | 6 +- .../META-INF/native-image/reflect-config.json | 12 ++++ 9 files changed, 187 insertions(+), 16 deletions(-) create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/settings/DTDEnabled.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/settings/XMLDTDSettings.java diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java index 20c18d643..12c5223ea 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java @@ -338,8 +338,9 @@ public Collection getAllDocuments() { } @Override - public void validate(DOMDocument document, Map validationArgs) { - xmlTextDocumentService.validate(document, validationArgs); + public void validate(DOMDocument document, Map validationArgs, + XMLValidationRootSettings validationSettings) { + xmlTextDocumentService.validate(document, validationArgs, validationSettings); } public XMLCapabilityManager getCapabilityManager() { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java index 9f2ecf911..68b3a400a 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java @@ -204,7 +204,7 @@ public XMLTextDocumentService(XMLLanguageServer xmlLanguageServer) { this.limitExceededWarner = null; this.xmlValidatorDelayer = new ModelValidatorDelayer((document) -> { DOMDocument xmlDocument = document.getModel(); - validate(xmlDocument, Collections.emptyMap()); + validate(xmlDocument, Collections.emptyMap(), null); getXMLLanguageService().getDocumentLifecycleParticipants().forEach(participant -> { try { @@ -653,7 +653,7 @@ private void triggerValidationFor(Collection> doc xmlLanguageServer.schedule(() -> { documents.forEach(document -> { try { - validate(document.getModel(), Collections.emptyMap()); + validate(document.getModel(), Collections.emptyMap(), null); } catch (CancellationException e) { // Ignore the error and continue to validate other documents } @@ -692,7 +692,7 @@ void validate(TextDocument document, boolean withDelay) throws CancellationExcep } else { CompletableFuture.runAsync(() -> { DOMDocument xmlDocument = ((ModelTextDocument) document).getModel(); - validate(xmlDocument, Collections.emptyMap()); + validate(xmlDocument, Collections.emptyMap(), null); getXMLLanguageService().getDocumentLifecycleParticipants().forEach(participant -> { try { participant.didOpen(xmlDocument); @@ -708,19 +708,22 @@ void validate(TextDocument document, boolean withDelay) throws CancellationExcep /** * Validate and publish diagnostics for the given DOM document. * - * @param xmlDocument the DOM document. - * @param validationArgs the validation arguments. + * @param xmlDocument the DOM document. + * @param validationArgs the validation arguments. + * @param validationSettings * * @throws CancellationException when the DOM document content changed and * diagnostics must be stopped. */ - void validate(DOMDocument xmlDocument, Map validationArgs) throws CancellationException { + void validate(DOMDocument xmlDocument, Map validationArgs, + XMLValidationRootSettings validationSettings) + throws CancellationException { CancelChecker cancelChecker = xmlDocument.getCancelChecker(); cancelChecker.checkCanceled(); getXMLLanguageService().publishDiagnostics(xmlDocument, params -> xmlLanguageServer.getLanguageClient().publishDiagnostics(params), (doc) -> triggerValidationFor(doc, TriggeredBy.Other), - sharedSettings.getValidationSettings(), + validationSettings != null ? validationSettings : sharedSettings.getValidationSettings(), validationArgs, cancelChecker); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationFileCommand.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationFileCommand.java index 34842ff8e..3b9d23fe5 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationFileCommand.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationFileCommand.java @@ -15,6 +15,7 @@ import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; +import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationRootSettings; import org.eclipse.lemminx.services.IXMLDocumentProvider; import org.eclipse.lemminx.services.IXMLValidationService; import org.eclipse.lemminx.services.extensions.commands.AbstractDOMDocumentCommandHandler; @@ -59,15 +60,25 @@ protected Object executeCommand(DOMDocument document, ExecuteCommandParams param CancelChecker cancelChecker) throws Exception { // Validation args can contains the external resource ('url' as key map) to // force do download. - JsonObject validationArgs = params.getArguments().size() > 1 ? (JsonObject) params.getArguments().get(1) : null; + JsonObject validationArgs = getJsonObject(params, 1); + JsonObject validationSettings = getJsonObject(params, 2); // 1. remove the referenced grammar in the XML file from the Xerces grammar pool // (used by the Xerces validation) and the content model documents cache (used // by the XML completion/hover based on the grammar) contentModelManager.evictCacheFor(document); // 2. trigger the validation for the given XML file Map map = JSONUtility.toModel(validationArgs, Map.class); - validationService.validate(document, map); + XMLValidationRootSettings settings = JSONUtility.toModel(validationSettings, XMLValidationRootSettings.class); + validationService.validate(document, map, settings); return null; } + private static JsonObject getJsonObject(ExecuteCommandParams params, int index) { + if (params.getArguments().size() <= index) { + return null; + } + Object result = params.getArguments().get(index); + return result instanceof JsonObject ? (JsonObject) result : null; + } + } \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/XMLValidator.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/XMLValidator.java index 5a8c10a70..9073053a8 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/XMLValidator.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/XMLValidator.java @@ -35,8 +35,10 @@ import org.eclipse.lemminx.dom.XMLModel; import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; import org.eclipse.lemminx.extensions.contentmodel.participants.XMLSyntaxErrorCode; +import org.eclipse.lemminx.extensions.contentmodel.settings.DTDEnabled; import org.eclipse.lemminx.extensions.contentmodel.settings.NamespacesEnabled; import org.eclipse.lemminx.extensions.contentmodel.settings.SchemaEnabled; +import org.eclipse.lemminx.extensions.contentmodel.settings.XMLDTDSettings; import org.eclipse.lemminx.extensions.contentmodel.settings.XMLNamespacesSettings; import org.eclipse.lemminx.extensions.contentmodel.settings.XMLSchemaSettings; import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings; @@ -86,8 +88,10 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR LSPXMLEntityManager entityManager = new LSPXMLEntityManager(reporterForXML, grammarPool); try { + boolean dtdValidationEnabled = !isDisableOnlyDTDValidation(document, validationSettings); LSPXMLParserConfiguration configuration = new LSPXMLParserConfiguration(grammarPool, - isDisableOnlyDTDValidation(document), reporterForXML, reporterForGrammar, entityManager, + !dtdValidationEnabled, reporterForXML, reporterForGrammar, + entityManager, validationSettings); if (entityResolver != null) { @@ -119,6 +123,8 @@ && isSchemaValidationEnabled(document, validationSettings) || (!hasRelaxNG && document.hasExternalGrammar()); if (hasSchemaGrammar && !schemaValidationEnabled) { hasGrammar = false; + } else if (document.hasDTD() && !dtdValidationEnabled) { + hasGrammar = false; } parser.setFeature("http://xml.org/sax/features/validation", hasGrammar); //$NON-NLS-1$ @@ -324,7 +330,7 @@ private static boolean hasExternalSchemaGrammar(DOMDocument document) { * @param document the DOM document * @return true is DTD validation must be disabled and false otherwise. */ - private static boolean isDisableOnlyDTDValidation(DOMDocument document) { + private static boolean isDisableOnlyDTDValidation(DOMDocument document, XMLValidationSettings validationSettings) { Map externalGrammarLocation = document.getExternalGrammarLocation(); if (externalGrammarLocation != null && externalGrammarLocation.containsKey(IExternalGrammarLocationProvider.DOCTYPE)) { @@ -343,12 +349,41 @@ private static boolean isDisableOnlyDTDValidation(DOMDocument document) { } DOMDocumentType docType = document.getDoctype(); if (docType.getKindNode() != null) { - return false; + return !isDTDValidationEnabled(document, validationSettings); } // Disable the DTD validation only if there are not node.isDTDElementDecl() || node.isDTDAttListDecl()); } + private static boolean isValidDTDLocation(DOMDocument document) { + DOMDocumentType docType = document.getDoctype(); + if (docType == null) { + return false; + } + return isValidLocation(document.getDocumentURI(), docType.getPublicId()); + } + + private static boolean isDTDValidationEnabled(DOMDocument document, XMLValidationSettings validationSettings) { + if (validationSettings == null) { + return true; + } + DTDEnabled enabled = DTDEnabled.always; + XMLDTDSettings dtdSettings = validationSettings.getDtd(); + if (dtdSettings != null && dtdSettings.getEnabled() != null) { + enabled = dtdSettings.getEnabled(); + } + switch (enabled) { + case always: + return true; + case never: + return false; + case onValidDTD: + return isValidDTDLocation(document); + default: + return true; + } + } + /** * Warn if XML document is not bound to a grammar according the settings * diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/settings/DTDEnabled.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/settings/DTDEnabled.java new file mode 100644 index 000000000..6baf9e717 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/settings/DTDEnabled.java @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2023 Red Hat Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + */ +package org.eclipse.lemminx.extensions.contentmodel.settings; + +public enum DTDEnabled { + always, never, onValidDTD; +} \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/settings/XMLDTDSettings.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/settings/XMLDTDSettings.java new file mode 100644 index 000000000..e67df76d5 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/settings/XMLDTDSettings.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2023 Red Hat Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + */ +package org.eclipse.lemminx.extensions.contentmodel.settings; + +/** + * XML DTD settings. + * + */ +public class XMLDTDSettings { + + public XMLDTDSettings() { + setEnabled(DTDEnabled.always); + } + + private DTDEnabled enabled; + + public void setEnabled(DTDEnabled enabled) { + this.enabled = enabled; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((enabled == null) ? 0 : enabled.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + XMLDTDSettings other = (XMLDTDSettings) obj; + if (enabled != other.enabled) { + return false; + } + return true; + } + + public DTDEnabled getEnabled() { + return enabled; + } +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/settings/XMLValidationSettings.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/settings/XMLValidationSettings.java index 86d7c70a3..317963299 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/settings/XMLValidationSettings.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/settings/XMLValidationSettings.java @@ -26,6 +26,8 @@ public class XMLValidationSettings { private XMLNamespacesSettings namespaces; private XMLSchemaSettings schema; + + private XMLDTDSettings dtd; private boolean disallowDocTypeDecl; @@ -50,6 +52,7 @@ public XMLValidationSettings() { setResolveExternalEntities(false); setNamespaces(new XMLNamespacesSettings()); setSchema(new XMLSchemaSettings()); + setDtd(new XMLDTDSettings()); setXInclude(new XMLXIncludeSettings()); } @@ -107,6 +110,24 @@ public void setSchema(XMLSchemaSettings schema) { this.schema = schema; } + /** + * Returns the XML DTD validation settings. + * + * @return the XML DTD validation settings. + */ + public XMLDTDSettings getDtd() { + return dtd; + } + + /** + * Set the XML DTD validation settings. + * + * @param schema the XML DTD validation settings. + */ + public void setDtd(XMLDTDSettings dtd) { + this.dtd = dtd; + } + public void setNoGrammar(String noGrammar) { this.noGrammar = noGrammar; } @@ -203,6 +224,7 @@ public XMLValidationSettings merge(XMLValidationSettings settings) { if (settings != null) { this.namespaces = settings.namespaces; this.schema = settings.schema; + this.dtd = settings.dtd; this.enabled = settings.enabled; this.noGrammar = settings.noGrammar; this.disallowDocTypeDecl = settings.disallowDocTypeDecl; @@ -231,6 +253,7 @@ public int hashCode() { result = prime * result + ((noGrammar == null) ? 0 : noGrammar.hashCode()); result = prime * result + (resolveExternalEntities ? 1231 : 1237); result = prime * result + ((schema == null) ? 0 : schema.hashCode()); + result = prime * result + ((dtd == null) ? 0 : dtd.hashCode()); result = prime * result + ((xInclude == null) ? 0 : xInclude.hashCode()); return result; } @@ -288,6 +311,13 @@ public boolean equals(Object obj) { } else if (!schema.equals(other.schema)) { return false; } + if (dtd == null) { + if (other.dtd != null) { + return false; + } + } else if (!dtd.equals(other.dtd)) { + return false; + } return true; } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/IXMLValidationService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/IXMLValidationService.java index 9685b81ef..0f5d32064 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/IXMLValidationService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/IXMLValidationService.java @@ -16,6 +16,7 @@ import java.util.Map; import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationRootSettings; /** * XML Document validation service available to XML LS extensions @@ -31,7 +32,8 @@ public interface IXMLValidationService { * @param document the XML document * @param validationArgs validation arguments. */ - void validate(DOMDocument document, Map validationArgs); + void validate(DOMDocument document, Map validationArgs, + XMLValidationRootSettings validationSettings); /** * Performs XML document validation @@ -39,7 +41,7 @@ public interface IXMLValidationService { * @param document the XML document */ default void validate(DOMDocument document) { - validate(document, Collections.emptyMap()); + validate(document, Collections.emptyMap(), null); } } diff --git a/org.eclipse.lemminx/src/main/resources/META-INF/native-image/reflect-config.json b/org.eclipse.lemminx/src/main/resources/META-INF/native-image/reflect-config.json index daecd3a5a..aedd7cc2f 100644 --- a/org.eclipse.lemminx/src/main/resources/META-INF/native-image/reflect-config.json +++ b/org.eclipse.lemminx/src/main/resources/META-INF/native-image/reflect-config.json @@ -409,6 +409,18 @@ "name": "org.eclipse.lemminx.extensions.contentmodel.settings.SchemaEnabled", "allDeclaredFields": true }, + { + "name": "org.eclipse.lemminx.extensions.contentmodel.settings.XMLDTDSettings", + "allDeclaredFields": true, + "methods": [{ + "name": "", + "parameterTypes": [] + }] + }, + { + "name": "org.eclipse.lemminx.extensions.contentmodel.settings.DTDEnabled", + "allDeclaredFields": true + }, { "name": "org.eclipse.lemminx.settings.XMLSymbolExpressionFilter", "allDeclaredFields": true,