Skip to content

Commit

Permalink
Disable XSD validation when xsi:schemaLocation doesn't declare the
Browse files Browse the repository at this point in the history
namespace for the document element root.

See #951

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Jan 9, 2021
1 parent 0d812fb commit 9028503
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
package org.eclipse.lemminx.extensions.contentmodel.participants.diagnostics;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -28,8 +30,11 @@
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMDocumentType;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.SchemaLocationHint;
import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lemminx.extensions.contentmodel.participants.XMLSyntaxErrorCode;
import org.eclipse.lemminx.extensions.contentmodel.settings.SchemaEnabled;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLSchemaSettings;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings;
import org.eclipse.lemminx.services.extensions.diagnostics.LSPContentHandler;
import org.eclipse.lemminx.uriresolver.CacheResourceDownloadingException;
Expand Down Expand Up @@ -82,19 +87,20 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR
// Add LSP content handler to stop XML parsing if monitor is canceled.
parser.setContentHandler(new LSPContentHandler(monitor));

boolean hasSchemaGrammar = document.hasSchemaLocation() || document.hasNoNamespaceSchemaLocation()
// warn if XML document is not bound to a grammar according the settings
warnNoGrammar(document, diagnostics, validationSettings);
// Update external grammar location (file association)
updateExternalGrammarLocation(document, parser);

boolean hasSchemaLocation = document.hasSchemaLocation();
boolean hasSchemaGrammar = hasSchemaLocation || document.hasNoNamespaceSchemaLocation()
|| hasExternalSchemaGrammar(document);
boolean hasGrammar = document.hasDTD() || hasSchemaGrammar || document.hasExternalGrammar();
// If diagnostics for Schema preference is enabled
if ((validationSettings == null) || validationSettings.isSchema()) {

updateExternalGrammarLocation(document, parser);
parser.setFeature("http://apache.org/xml/features/validation/schema", hasSchemaGrammar); //$NON-NLS-1$

// warn if XML document is not bound to a grammar according the settings
warnNoGrammar(document, diagnostics, validationSettings);
} else {
hasGrammar = false; // validation for Schema was disabled
boolean schemaValidationEnabled = hasSchemaGrammar && isSchemaValidationEnabled(document, validationSettings);
parser.setFeature("http://apache.org/xml/features/validation/schema", schemaValidationEnabled); //$NON-NLS-1$
boolean hasGrammar = document.hasDTD() || hasSchemaGrammar || document.hasExternalGrammar();
if (hasSchemaGrammar && !schemaValidationEnabled) {
hasGrammar = false;
}
parser.setFeature("http://xml.org/sax/features/validation", hasGrammar); //$NON-NLS-1$

Expand All @@ -114,6 +120,55 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR
}
}

private static boolean isSchemaValidationEnabled(DOMDocument document, XMLValidationSettings validationSettings) {
if (validationSettings == null) {
return true;
}
SchemaEnabled enabled = SchemaEnabled.always;
XMLSchemaSettings schemaSettings = validationSettings.getSchema();
if (schemaSettings != null && schemaSettings.getEnabled() != null) {
enabled = schemaSettings.getEnabled();
}
switch (enabled) {
case always:
return true;
case never:
return false;
case onValidSchema:
return isValidSchemaLocation(document);
default:
return true;
}
}

/**
* Returns true if the given DOM document declares a xsi:schemaLocation hint for
* the document element and false otherwise.
*
* @param document the DOM document.
* @return true if the given DOM document declares a xsi:schemaLocation hint for
* the document element and false otherwise.
*/
private static boolean isValidSchemaLocation(DOMDocument document) {
if (!document.hasSchemaLocation()) {
return false;
}
String namespaceURI = document.getNamespaceURI();
SchemaLocationHint hint = document.getSchemaLocation().getLocationHint(namespaceURI);
if (hint == null) {
return false;
}
String location = hint.getHint();
if (StringUtils.isBlank(location)) {
return false;
}
try (InputStream is = new URL(location).openStream()) {
return true;
} catch (Exception e) {
return false;
}
}

private static boolean hasExternalSchemaGrammar(DOMDocument document) {
if (document.getExternalGrammarFromNamespaceURI() != null) {
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2020 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 SchemaEnabled {
always, never, onValidSchema;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2020 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 class XMLSchemaSettings {

public XMLSchemaSettings() {
setEnabled(SchemaEnabled.always);
}

private SchemaEnabled enabled;

public void setEnabled(SchemaEnabled enabled) {
this.enabled = enabled;
}

public SchemaEnabled getEnabled() {
return enabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*/
public class XMLValidationSettings {

private Boolean schema;
private XMLSchemaSettings schema;

private Boolean enabled;

Expand All @@ -31,7 +31,7 @@ public class XMLValidationSettings {
/**
* This severity preference to mark the root element of XML document which is
* not bound to a XML Schema/DTD.
*
*
* Values are {ignore, hint, info, warning, error}
*/
private String noGrammar;
Expand All @@ -40,7 +40,7 @@ public class XMLValidationSettings {

public XMLValidationSettings() {
// set defaults
setSchema(true);
//setSchema(new XMLSchemaSettings());
setEnabled(true);
setDisallowDocTypeDecl(false);
setResolveExternalEntities(false);
Expand All @@ -61,16 +61,18 @@ public void setEnabled(boolean enabled) {
}

/**
* @return the schema
* Returns the XML Schema validation settings.
*
* @return the XML Schema validation settings.
*/
public boolean isSchema() {
public XMLSchemaSettings getSchema() {
return schema;
}

/**
* @param schema the schema to set
*/
public void setSchema(boolean schema) {
public void setSchema(XMLSchemaSettings schema) {
this.schema = schema;
}

Expand All @@ -85,7 +87,7 @@ public String getNoGrammar() {
/**
* Returns true if a fatal error is thrown if the incoming document contains a
* DOCTYPE declaration and false otherwise.
*
*
* @return true if a fatal error is thrown if the incoming document contains a
* DOCTYPE declaration and false otherwise.
*/
Expand All @@ -96,7 +98,7 @@ public boolean isDisallowDocTypeDecl() {
/**
* Set true if a fatal error is thrown if the incoming document contains a
* DOCTYPE declaration and false otherwise.
*
*
* @param disallowDocTypeDecl disallow DOCTYPE declaration.
*/
public void setDisallowDocTypeDecl(boolean disallowDocTypeDecl) {
Expand All @@ -105,7 +107,7 @@ public void setDisallowDocTypeDecl(boolean disallowDocTypeDecl) {

/**
* Returns true if external entities must be resolved and false otherwise.
*
*
* @return true if external entities must be resolved and false otherwise.
*/
public boolean isResolveExternalEntities() {
Expand All @@ -114,7 +116,7 @@ public boolean isResolveExternalEntities() {

/**
* Set true if external entities must be resolved and false otherwise.
*
*
* @param resolveExternalEntities resolve extrenal entities
*/
public void setResolveExternalEntities(boolean resolveExternalEntities) {
Expand All @@ -124,7 +126,7 @@ public void setResolveExternalEntities(boolean resolveExternalEntities) {
/**
* Returns the <code>noGrammar</code> severity according the given settings and
* {@link DiagnosticSeverity#Hint} otherwise.
*
*
* @param validationSettings the validation settings
* @return the <code>noGrammar</code> severity according the given settings and
* {@link DiagnosticSeverity#Hint} otherwise.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMParser;
import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings;
import org.eclipse.lemminx.extensions.contentmodel.settings.SchemaEnabled;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLSchemaSettings;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings;
import org.eclipse.lemminx.extensions.generators.FileContentGeneratorManager;
import org.eclipse.lemminx.extensions.generators.FileContentGeneratorSettings;
Expand Down Expand Up @@ -441,15 +443,17 @@ public static Range r(int startLine, int startCharacter, int endLine, int endCha
return new Range(new Position(startLine, startCharacter), new Position(endLine, endCharacter));
}

public static ContentModelSettings getContentModelSettings(boolean isEnabled, boolean isSchema) {
public static ContentModelSettings getContentModelSettings(boolean isEnabled, SchemaEnabled schemaEnabled) {
ContentModelSettings settings = new ContentModelSettings();
settings.setUseCache(false);
XMLValidationSettings problems = new XMLValidationSettings();
problems.setNoGrammar("ignore");
settings.setValidation(problems);
XMLValidationSettings diagnostics = new XMLValidationSettings();
diagnostics.setEnabled(isEnabled);
diagnostics.setSchema(isSchema);
XMLSchemaSettings schemaSettings = new XMLSchemaSettings();
schemaSettings.setEnabled(schemaEnabled);
diagnostics.setSchema(schemaSettings);
settings.setValidation(diagnostics);
return settings;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.extensions.contentmodel.participants.XMLSchemaErrorCode;
import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings;
import org.eclipse.lemminx.extensions.contentmodel.settings.SchemaEnabled;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings;
import org.eclipse.lemminx.services.XMLLanguageService;
import org.eclipse.lemminx.settings.EnforceQuoteStyle;
Expand Down Expand Up @@ -1027,12 +1028,48 @@ public void diagnosticsWithCatalogAndXSDInclude() throws BadLocationException {
XMLAssert.testDiagnosticsFor(xml, "src/test/resources/catalogs/include/catalog-include.xml", diagnostic);
}

@Test
public void noHintSchemaLocationForRootElement() {
// Here the xsi:schemaLocation doens't declare the hint for
// http://www.eclipse.org/oomph/setup/1.0 (used in the root element)
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + //
"<setup:Configuration \r\n" + //
" xmi:version=\"2.0\"\r\n" + //
" xmlns:xmi=\"http://www.omg.org/XMI\"\r\n" + //
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \r\n" + //
" xmlns:setup=\"http://www.eclipse.org/oomph/setup/1.0\"\r\n" + //
" xmlns:setup.p2=\"http://www.eclipse.org/oomph/setup/p2/1.0\"\r\n" + //
" xmlns:workbench=\"http://www.eclipse.org/oomph/setup/workbench/1.0\"\r\n" + //
" xsi:schemaLocation=\"http://www.eclipse.org/oomph/setup/workbench/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/Workbench.ecore\"\r\n"
+ //
" label=\"Gael Eclipse Installation\"> \r\n" + //
" <installation name=\"com.github.glhez.eclipse.install\" label=\"Gael Eclipse Installation Installation\">\r\n"
+ //
" <setupTask xsi:type=\"setup.p2:P2Task\" label=\"Oomph Setup Task\">\r\n" + //
" <requirement name=\"org.eclipse.oomph.setup.feature.group\"/>\r\n" + //
" <repository url=\"${oomph.update.url}\"/>\r\n" + //
" </setupTask> \r\n" + //
" </installation>\r\n" + //
"</setup:Configuration>";

// always
XMLAssert.testDiagnosticsFor(xml,
d(1, 1, 1, 20, XMLSchemaErrorCode.cvc_elt_1_a,
"cvc-elt.1.a: Cannot find the declaration of element 'setup:Configuration'."), //
d(11, 67, 11, 67, XMLSchemaErrorCode.cvc_elt_4_2,
"cvc-elt.4.2: Cannot resolve 'setup.p2:P2Task' to a type definition for element 'setupTask'."));

// on schema valid
ContentModelSettings settings = XMLAssert.getContentModelSettings(true, SchemaEnabled.onValidSchema);
XMLAssert.testDiagnosticsFor(xml, null, null, null, true, settings);
}

private static void testDiagnosticsFor(String xml, Diagnostic... expected) {
XMLAssert.testDiagnosticsFor(xml, "src/test/resources/catalogs/catalog.xml", expected);
}

private static void testDiagnosticsDisabledValidation(String xml) {
ContentModelSettings settings = XMLAssert.getContentModelSettings(true, false);
ContentModelSettings settings = XMLAssert.getContentModelSettings(true, SchemaEnabled.never);
XMLAssert.testDiagnosticsFor(xml, "src/test/resources/catalogs/catalog.xml", null, null, true, settings);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import org.eclipse.lemminx.XMLAssert;
import org.eclipse.lemminx.extensions.contentmodel.participants.XMLSyntaxErrorCode;
import org.eclipse.lemminx.extensions.contentmodel.settings.SchemaEnabled;
import org.eclipse.lemminx.settings.EnforceQuoteStyle;
import org.eclipse.lemminx.settings.QuoteStyle;
import org.eclipse.lemminx.settings.SharedSettings;
Expand Down Expand Up @@ -630,7 +631,7 @@ public void testMissingQuotesForAttributeSingleQuotes() throws Exception {
@Test
public void testOpenQuoteExpectedDisabledPreference() throws Exception {
String xml = " <InstdAmt Ccy==\"JPY\">10000000</InstdAmt>";
testDiagnosticsFor(xml, null, null, null, true, XMLAssert.getContentModelSettings(false, true)); // validation
testDiagnosticsFor(xml, null, null, null, true, XMLAssert.getContentModelSettings(false, SchemaEnabled.always)); // validation
// is
// disabled
}
Expand Down
Loading

0 comments on commit 9028503

Please sign in to comment.