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 11, 2021
1 parent 0d812fb commit d3dca18
Show file tree
Hide file tree
Showing 8 changed files with 297 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,12 @@
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMDocumentType;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.NoNamespaceSchemaLocation;
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 +88,24 @@ 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()
|| hasExternalSchemaGrammar(document);
boolean hasGrammar = document.hasDTD() || hasSchemaGrammar || document.hasExternalGrammar();
// If diagnostics for Schema preference is enabled
if ((validationSettings == null) || validationSettings.isSchema()) {
// 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);

updateExternalGrammarLocation(document, parser);
parser.setFeature("http://apache.org/xml/features/validation/schema", hasSchemaGrammar); //$NON-NLS-1$
boolean hasSchemaLocation = document.hasSchemaLocation();
boolean hasNoNamespaceSchemaLocation = document.hasNoNamespaceSchemaLocation();
boolean hasSchemaGrammar = hasSchemaLocation || hasNoNamespaceSchemaLocation
|| hasExternalSchemaGrammar(document);
boolean schemaValidationEnabled = (hasSchemaGrammar
&& isSchemaValidationEnabled(document, validationSettings)
|| (hasNoNamespaceSchemaLocation
&& isNoNamespaceSchemaValidationEnabled(document, validationSettings)));
parser.setFeature("http://apache.org/xml/features/validation/schema", schemaValidationEnabled); //$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 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 +125,116 @@ 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 root element is valid and false otherwise.
*
* The xsi:schemaLocation is valid if:
*
* <ul>
* <li>xsi:schemaLocation defines an URI for the namespace of the document
* element.</li>
* <li>the URI can be opened</li>
* </ul>
*
* @param document the DOM document.
* @return true if the given DOM document declares a xsi:schemaLocation hint for
* the document root element is valid 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 isNoNamespaceSchemaValidationEnabled(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 isValidNoNamespaceSchemaLocation(document);
default:
return true;
}
}

/**
* Returns true if the given DOM document declares a
* xsi:noNamespaceSchemaLocation which is valid and false otherwise.
*
* The xsi:noNamespaceSchemaLocation is valid if:
*
* <ul>
* <li>xsi:noNamespaceSchemaLocation defines an URI.</li>
* <li>the URI can be opened</li>
* </ul>
*
* @param document the DOM document.
* @return true if the given DOM document declares a xsi:schemaLocation hint for
* the document root element is valid and false otherwise.
*/
private static boolean isValidNoNamespaceSchemaLocation(DOMDocument document) {
NoNamespaceSchemaLocation noNamespaceSchemaLocation = document.getNoNamespaceSchemaLocation();
if (noNamespaceSchemaLocation == null) {
return false;
}
String location = noNamespaceSchemaLocation.getLocation();
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
Loading

0 comments on commit d3dca18

Please sign in to comment.