[566183] Restrict which external entities are automatically allowed,
         since some are required for proper XHTML functionality in
         JSPs.
diff --git a/web/tests/org.eclipse.jst.jsp.core.tests/src/org/eclipse/jst/jsp/core/tests/contentmodels/TestFixedCMDocuments.java b/web/tests/org.eclipse.jst.jsp.core.tests/src/org/eclipse/jst/jsp/core/tests/contentmodels/TestFixedCMDocuments.java
index 6040131..997d4d9 100644
--- a/web/tests/org.eclipse.jst.jsp.core.tests/src/org/eclipse/jst/jsp/core/tests/contentmodels/TestFixedCMDocuments.java
+++ b/web/tests/org.eclipse.jst.jsp.core.tests/src/org/eclipse/jst/jsp/core/tests/contentmodels/TestFixedCMDocuments.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2020 IBM Corporation and others.
+ * Copyright (c) 2007, 2021 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
  * which accompanies this distribution, and is available at
@@ -12,16 +12,27 @@
  *******************************************************************************/
 package org.eclipse.jst.jsp.core.tests.contentmodels;
 
+import java.io.IOException;
+
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Path;
 import org.eclipse.jst.jsp.core.internal.contentmodel.JSPCMDocumentFactory;
+import org.eclipse.jst.jsp.core.tests.ProjectUtil;
 import org.eclipse.wst.html.core.internal.contentmodel.JSP11Namespace;
 import org.eclipse.wst.html.core.internal.contentmodel.JSP20Namespace;
 import org.eclipse.wst.html.core.internal.contentmodel.JSP21Namespace;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
 import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration;
 import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument;
 import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
 import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap;
 import org.eclipse.wst.xml.core.internal.contentmodel.CMNode;
+import org.eclipse.wst.xml.core.internal.modelquery.ModelQueryUtil;
 import org.eclipse.wst.xml.core.internal.provisional.contentmodel.CMDocType;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.w3c.dom.Element;
 
 import junit.framework.TestCase;
 
@@ -272,6 +283,22 @@
 		checkDocument(CMDocType.TAG20_DOC_TYPE);
 	}
 	
+	public void testXmlJsp() throws IOException, CoreException {
+		ProjectUtil.createProject("testxmljsp", null, null);
+		ProjectUtil.copyBundleEntriesIntoWorkspace("testfiles/testxmljsp", "testxmljsp");
+		IStructuredModel model = null;
+		try {
+			model = StructuredModelManager.getModelManager().getModelForRead(ResourcesPlugin.getWorkspace().getRoot().getFile(new Path("testxmljsp/test.jsp")));
+			Element body = (Element) ((IDOMModel) model).getDocument().getElementsByTagName("body").item(0);
+			assertNotNull("XHTML Transitional DTD did not load for JSP document (check if trusted according to DTDCorePlugin)", ModelQueryUtil.getModelQuery(model).getCMElementDeclaration(body));
+		}
+		finally {
+			if (model != null) {
+				model.releaseFromRead();
+			}
+		}
+	}
+
 	private void verifyAttributeDeclaration(CMElementDeclaration elemDecl, CMNode attr) {
 		assertTrue(attr.getNodeType() == CMNode.ATTRIBUTE_DECLARATION);
 		assertNotNull("no name on an attribute declaration", attr.getNodeName());
diff --git a/web/tests/org.eclipse.jst.jsp.core.tests/testfiles/testxmljsp/test.jsp b/web/tests/org.eclipse.jst.jsp.core.tests/testfiles/testxmljsp/test.jsp
new file mode 100644
index 0000000..450a139
--- /dev/null
+++ b/web/tests/org.eclipse.jst.jsp.core.tests/testfiles/testxmljsp/test.jsp
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
+    pageEncoding="ISO-8859-1"%>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+<title>Test</title>
+</head>
+<body>
+Before Tag
+
+After Tag
+</body>
+</html>
diff --git a/xml/bundles/org.eclipse.wst.dtd.core/contentmodel/org/eclipse/wst/dtd/core/internal/contentmodel/DTDImpl.java b/xml/bundles/org.eclipse.wst.dtd.core/contentmodel/org/eclipse/wst/dtd/core/internal/contentmodel/DTDImpl.java
index eca56ac..c9463d9 100644
--- a/xml/bundles/org.eclipse.wst.dtd.core/contentmodel/org/eclipse/wst/dtd/core/internal/contentmodel/DTDImpl.java
+++ b/xml/bundles/org.eclipse.wst.dtd.core/contentmodel/org/eclipse/wst/dtd/core/internal/contentmodel/DTDImpl.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2001, 2011 IBM Corporation and others.
+ * Copyright (c) 2001, 2021 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
  * which accompanies this distribution, and is available at
@@ -18,12 +18,13 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import com.ibm.icu.util.StringTokenizer;
 
+import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.emf.common.notify.Adapter;
 import org.eclipse.emf.common.notify.Notifier;
 import org.eclipse.emf.common.notify.impl.AdapterFactoryImpl;
 import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.wst.dtd.core.internal.DTDCorePlugin;
 import org.eclipse.wst.dtd.core.internal.emf.DTDAnyContent;
 import org.eclipse.wst.dtd.core.internal.emf.DTDAttribute;
 import org.eclipse.wst.dtd.core.internal.emf.DTDBasicType;
@@ -66,6 +67,7 @@
 import org.eclipse.wst.xml.core.internal.contentmodel.basic.CMNodeListImpl;
 import org.eclipse.wst.xml.core.internal.contentmodel.util.CMDescriptionBuilder;
 
+import com.ibm.icu.util.StringTokenizer;
 
 public class DTDImpl {
 	static {
@@ -87,6 +89,7 @@
 	public static DTDFile buildDTDModel(String uri) {
 		DTDUtil dtdUtil = new DTDUtil();
 		dtdUtil.setexpandEntityReferences(true);
+		dtdUtil.setIsTrustedBase(isKnownURI(uri));
 		dtdUtil.parse(new ResourceSetImpl(), uri);
 		return dtdUtil.getDTDFile();
 	}
@@ -111,6 +114,54 @@
 		return isMulti ? -1 : 1;
 	}
 
+	/**
+	 * A URI is considered known if one of its fragments begins with the name
+	 * of a known contributor and the uri ends with one of its contributed URI
+	 * values. The fuzzy matching is needed to account for runtime differences
+	 * during development and production.
+	 *
+	 * @param dtdURI
+	 * @return
+	 */
+	static boolean isKnownURI(String dtdURI) {
+		/* nothing in the current workspace should be trusted */
+		if (dtdURI.contains(ResourcesPlugin.getWorkspace().getRoot().getLocation().toString())) {
+			return false;
+		}
+
+		String[] parts = dtdURI.split("/");
+		if (parts.length < 5) {
+			return false;
+		}
+		/* nothing remote should be trusted */
+		if (!parts[0].endsWith("file:")) {
+			return false;
+		}
+
+		String[] activeContributors = DTDCorePlugin.KNOWN_URIS.keySet().toArray(new String[0]);
+		/*
+		 * The last part at least must be the file name, and the first part
+		 * part of the installation location, so skip them
+		 */
+		for (int i = parts.length - 2; i > 0; i--) {
+			String part = parts[i];
+			for (int j = 0; j < activeContributors.length; j++) {
+				if (part.startsWith(activeContributors[j])) {
+					part = activeContributors[j];
+					String[] contributedUris = null;
+					if ((contributedUris = DTDCorePlugin.KNOWN_URIS.get(part)) != null) {
+						for (int k = 0; k < contributedUris.length; k++) {
+							if (dtdURI.endsWith(contributedUris[k])) {
+								return true;
+							}
+						}
+					}
+				}
+			}
+		}
+		return false;
+	}
+
 	public static class DTDAdapterFactoryImpl extends AdapterFactoryImpl {
 		public Adapter createAdapter(Notifier target) {
 			Adapter result = null;
diff --git a/xml/bundles/org.eclipse.wst.dtd.core/emfmodel/org/eclipse/wst/dtd/core/internal/emf/util/DTDUtil.java b/xml/bundles/org.eclipse.wst.dtd.core/emfmodel/org/eclipse/wst/dtd/core/internal/emf/util/DTDUtil.java
index 88490dd..c162dbd 100644
--- a/xml/bundles/org.eclipse.wst.dtd.core/emfmodel/org/eclipse/wst/dtd/core/internal/emf/util/DTDUtil.java
+++ b/xml/bundles/org.eclipse.wst.dtd.core/emfmodel/org/eclipse/wst/dtd/core/internal/emf/util/DTDUtil.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2001, 2005 IBM Corporation and others.
+ * Copyright (c) 2001, 2021 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
  * which accompanies this distribution, and is available at
@@ -64,6 +64,7 @@
 	private DTDParser parser;
 
 	private Vector errorMsgs = new Vector();
+	private boolean isTrustedBase;
 
 	private static Hashtable utilCache = new Hashtable();
 
@@ -101,6 +102,7 @@
 			if (expandEntityReferences) {
 				parser.setExpandEntityReferences(expandEntityReferences);
 			}
+			parser.setIsTrustedBase(isTrustedBase);
 			parser.parse(filename);
 		}
 		catch (IOException ex) {
@@ -153,6 +155,10 @@
 		this.expandEntityReferences = expandEntityReferences;
 	}
 
+	public void setIsTrustedBase(boolean trusted) {
+		this.isTrustedBase = trusted;
+	}
+
 	public boolean getExpandEntityReferences() {
 		return expandEntityReferences;
 	}
diff --git a/xml/bundles/org.eclipse.wst.dtd.core/saxparser/org/eclipse/wst/dtd/core/internal/saxparser/DTDParser.java b/xml/bundles/org.eclipse.wst.dtd.core/saxparser/org/eclipse/wst/dtd/core/internal/saxparser/DTDParser.java
index 8fd6a8c..cf029bc 100644
--- a/xml/bundles/org.eclipse.wst.dtd.core/saxparser/org/eclipse/wst/dtd/core/internal/saxparser/DTDParser.java
+++ b/xml/bundles/org.eclipse.wst.dtd.core/saxparser/org/eclipse/wst/dtd/core/internal/saxparser/DTDParser.java
@@ -145,7 +145,7 @@
 				currentDTD.setIsExceptionDuringParse(true);
 		}
 		catch (Exception e) {
-			Logger.logException(e);
+			Logger.log(Logger.ERROR_DEBUG, e.getMessage(), e);
 			if (currentDTD != null)
 				currentDTD.setIsExceptionDuringParse(true);
 		}
@@ -171,6 +171,15 @@
 		}
 	}
 
+	/**
+	 * Affects the resolution of external entities by asserting that the parser
+	 * started with a trusted/known DTD
+	 * @param nowTrusted
+	 */
+	public void setIsTrustedBase(boolean nowTrusted) {
+		this.isTrustedBase = nowTrusted;
+	}
+
 	/*
 	 * @deprecated Entity references are always expanded.
 	 */
@@ -249,6 +258,12 @@
 
 	protected String attributeString = ""; //$NON-NLS-1$
 
+	/**
+	 * Is this a trusted (known) URI, or what that was referenced by a
+	 * known/trusted URI?
+	 */
+	boolean isTrustedBase;
+
 	public void attributeDecl(String eName, String aName, String type, String valueDefault, String value) throws SAXException {
 		startDeclaration(DeclNode.ATTLIST);
 		//declString = "<!ATTLIST"; //$NON-NLS-1$
@@ -578,8 +593,14 @@
 					break;
 				}
 				case DeclNode.EXTERNAL_ENTITY : {
-					if (!this.expandEntityReferences) {
-						break;
+					if (!this.isTrustedBase) {
+						if (!this.expandEntityReferences) {
+							break;
+						}
+						boolean resolveExternalEntities = InstanceScope.INSTANCE.getNode(XMLCorePlugin.getDefault().getBundle().getSymbolicName()).getBoolean(XMLCorePreferenceNames.RESOLVE_EXTERNAL_ENTITIES, false);
+						if (!resolveExternalEntities) {
+							break;
+						}
 					}
 				}
 				case DeclNode.INTERNAL_ENTITY : {
diff --git a/xml/bundles/org.eclipse.wst.dtd.core/src/org/eclipse/wst/dtd/core/internal/DTDCorePlugin.java b/xml/bundles/org.eclipse.wst.dtd.core/src/org/eclipse/wst/dtd/core/internal/DTDCorePlugin.java
index 357601c..9896848 100644
--- a/xml/bundles/org.eclipse.wst.dtd.core/src/org/eclipse/wst/dtd/core/internal/DTDCorePlugin.java
+++ b/xml/bundles/org.eclipse.wst.dtd.core/src/org/eclipse/wst/dtd/core/internal/DTDCorePlugin.java
@@ -14,11 +14,23 @@
  *******************************************************************************/
 package org.eclipse.wst.dtd.core.internal;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Plugin;
+import org.eclipse.wst.xml.core.internal.XMLCorePlugin;
+import org.osgi.framework.BundleContext;
 
 public class DTDCorePlugin extends Plugin {
 	private static DTDCorePlugin instance;
 
+	public static Map<String, String[]> KNOWN_URIS;
+
 	public synchronized static DTDCorePlugin getInstance() {
 		return instance;
 	}
@@ -27,6 +39,32 @@
 		return instance;
 	}
 
+	@Override
+	public void start(BundleContext context) throws Exception {
+		super.start(context);
+
+		KNOWN_URIS = new HashMap<>();
+		IExtensionPoint extension = Platform.getExtensionRegistry().getExtensionPoint(XMLCorePlugin.getDefault().getBundle().getSymbolicName(), "catalogContributions");
+		IConfigurationElement[] configurationElements = extension.getConfigurationElements();
+		Map<String, List<String>> known = new HashMap<>();
+		for (int i = 0; i < configurationElements.length; i++) {
+			String contributor = configurationElements[i].getNamespaceIdentifier();
+			if (!known.containsKey(contributor)) {
+				known.put(contributor, new ArrayList<>());
+			}
+			IConfigurationElement[] elements = configurationElements[i].getChildren();
+			for (int j = 0; j < elements.length; j++) {
+				String uri = elements[j].getAttribute("uri");
+				if (uri != null && uri.length() > 0) {
+					known.get(contributor).add(uri);
+				}
+			}
+		}
+		known.forEach((contributor, uris) -> {
+			KNOWN_URIS.put(contributor, uris.toArray(new String[uris.size()]));
+		});
+	}
+
 	public DTDCorePlugin() {
 		super();
 		instance = this;