1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 */ 4 /* 5 * Licensed to the Apache Software Foundation (ASF) under one or more 6 * contributor license agreements. See the NOTICE file distributed with 7 * this work for additional information regarding copyright ownership. 8 * The ASF licenses this file to You under the Apache License, Version 2.0 9 * (the "License"); you may not use this file except in compliance with 10 * the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 package com.sun.org.apache.xerces.internal.jaxp.validation; 22 23 import java.io.IOException; 24 import java.util.Enumeration; 25 26 import javax.xml.parsers.DocumentBuilder; 27 import javax.xml.parsers.DocumentBuilderFactory; 28 import javax.xml.parsers.ParserConfigurationException; 29 import javax.xml.transform.Result; 30 import javax.xml.transform.Source; 31 import javax.xml.transform.dom.DOMResult; 32 import javax.xml.transform.dom.DOMSource; 33 34 import com.sun.org.apache.xerces.internal.impl.Constants; 35 import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter; 36 import com.sun.org.apache.xerces.internal.impl.validation.EntityState; 37 import com.sun.org.apache.xerces.internal.impl.validation.ValidationManager; 38 import com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator; 39 import com.sun.org.apache.xerces.internal.impl.xs.util.SimpleLocator; 40 import com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl; 41 import com.sun.org.apache.xerces.internal.util.NamespaceSupport; 42 import com.sun.org.apache.xerces.internal.util.SymbolTable; 43 import com.sun.org.apache.xerces.internal.util.XMLAttributesImpl; 44 import com.sun.org.apache.xerces.internal.util.XMLSymbols; 45 import com.sun.org.apache.xerces.internal.xni.NamespaceContext; 46 import com.sun.org.apache.xerces.internal.xni.QName; 47 import com.sun.org.apache.xerces.internal.xni.XMLString; 48 import com.sun.org.apache.xerces.internal.xni.XNIException; 49 import com.sun.org.apache.xerces.internal.xni.parser.XMLParseException; 50 import jdk.xml.internal.JdkXmlUtils; 51 import org.w3c.dom.Attr; 52 import org.w3c.dom.CDATASection; 53 import org.w3c.dom.Comment; 54 import org.w3c.dom.Document; 55 import org.w3c.dom.DocumentType; 56 import org.w3c.dom.Entity; 57 import org.w3c.dom.NamedNodeMap; 58 import org.w3c.dom.Node; 59 import org.w3c.dom.ProcessingInstruction; 60 import org.w3c.dom.Text; 61 import org.xml.sax.SAXException; 62 63 /** 64 * <p>A validator helper for <code>DOMSource</code>s.</p> 65 * 66 * @author Michael Glavassevich, IBM 67 * @version $Id: DOMValidatorHelper.java,v 1.9 2010-11-01 04:40:08 joehw Exp $ 68 */ 69 final class DOMValidatorHelper implements ValidatorHelper, EntityState { 70 71 // 72 // Constants 73 // 74 75 /** Chunk size (1024). */ 76 private static final int CHUNK_SIZE = (1 << 10); 77 78 /** Chunk mask (CHUNK_SIZE - 1). */ 79 private static final int CHUNK_MASK = CHUNK_SIZE - 1; 80 81 // property identifiers 82 83 /** Property identifier: error reporter. */ 84 private static final String ERROR_REPORTER = 85 Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY; 86 87 /** Property identifier: namespace context. */ 88 private static final String NAMESPACE_CONTEXT = 89 Constants.XERCES_PROPERTY_PREFIX + Constants.NAMESPACE_CONTEXT_PROPERTY; 90 91 /** Property identifier: XML Schema validator. */ 92 private static final String SCHEMA_VALIDATOR = 93 Constants.XERCES_PROPERTY_PREFIX + Constants.SCHEMA_VALIDATOR_PROPERTY; 94 95 /** Property identifier: symbol table. */ 96 private static final String SYMBOL_TABLE = 97 Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY; 98 99 /** Property identifier: validation manager. */ 100 private static final String VALIDATION_MANAGER = 101 Constants.XERCES_PROPERTY_PREFIX + Constants.VALIDATION_MANAGER_PROPERTY; 102 103 // 104 // Data 105 // 106 107 /** Error reporter. */ 108 private XMLErrorReporter fErrorReporter; 109 110 /** The namespace context of this document: stores namespaces in scope. **/ 111 private NamespaceSupport fNamespaceContext; 112 113 /** The namespace context of the DOMSource, includes context from ancestor nodes. **/ 114 private DOMNamespaceContext fDOMNamespaceContext = new DOMNamespaceContext(); 115 116 /** Schema validator. **/ 117 private XMLSchemaValidator fSchemaValidator; 118 119 /** Symbol table **/ 120 private SymbolTable fSymbolTable; 121 122 /** Validation manager. **/ 123 private ValidationManager fValidationManager; 124 125 /** Component manager. **/ 126 private XMLSchemaValidatorComponentManager fComponentManager; 127 128 /** Simple Locator. **/ 129 private final SimpleLocator fXMLLocator = new SimpleLocator(null, null, -1, -1, -1); 130 131 /** DOM document handler. **/ 132 private DOMDocumentHandler fDOMValidatorHandler; 133 134 /** DOM result augmentor. **/ 135 private final DOMResultAugmentor fDOMResultAugmentor = new DOMResultAugmentor(this); 136 137 /** DOM result builder. **/ 138 private final DOMResultBuilder fDOMResultBuilder = new DOMResultBuilder(); 139 140 /** Map for tracking unparsed entities. **/ 141 private NamedNodeMap fEntities = null; 142 143 /** Array for holding character data. **/ 144 private char [] fCharBuffer = new char[CHUNK_SIZE]; 145 146 /** Root node. **/ 147 private Node fRoot; 148 149 /** Current element. **/ 150 private Node fCurrentElement; 151 152 /** Fields for start element, end element and characters. **/ 153 final QName fElementQName = new QName(); 154 final QName fAttributeQName = new QName(); 155 final XMLAttributesImpl fAttributes = new XMLAttributesImpl(); 156 final XMLString fTempString = new XMLString(); 157 158 public DOMValidatorHelper(XMLSchemaValidatorComponentManager componentManager) { 159 fComponentManager = componentManager; 160 fErrorReporter = (XMLErrorReporter) fComponentManager.getProperty(ERROR_REPORTER); 161 fNamespaceContext = (NamespaceSupport) fComponentManager.getProperty(NAMESPACE_CONTEXT); 162 fSchemaValidator = (XMLSchemaValidator) fComponentManager.getProperty(SCHEMA_VALIDATOR); 163 fSymbolTable = (SymbolTable) fComponentManager.getProperty(SYMBOL_TABLE); 164 fValidationManager = (ValidationManager) fComponentManager.getProperty(VALIDATION_MANAGER); 165 } 166 167 /* 168 * ValidatorHelper methods 169 */ 170 171 public void validate(Source source, Result result) 172 throws SAXException, IOException { 173 if (result instanceof DOMResult || result == null) { 174 final DOMSource domSource = (DOMSource) source; 175 final DOMResult domResult = (DOMResult) result; 176 Node node = domSource.getNode(); 177 fRoot = node; 178 if (node != null) { 179 fComponentManager.reset(); 180 fValidationManager.setEntityState(this); 181 fDOMNamespaceContext.reset(); 182 String systemId = domSource.getSystemId(); 183 fXMLLocator.setLiteralSystemId(systemId); 184 fXMLLocator.setExpandedSystemId(systemId); 185 fErrorReporter.setDocumentLocator(fXMLLocator); 186 try { 187 // regardless of what type of node this is, fire start and end document events 188 setupEntityMap((node.getNodeType() == Node.DOCUMENT_NODE) ? (Document) node : node.getOwnerDocument()); 189 setupDOMResultHandler(domSource, domResult); 190 fSchemaValidator.startDocument(fXMLLocator, null, fDOMNamespaceContext, null); 191 validate(node); 192 fSchemaValidator.endDocument(null); 193 } 194 catch (XMLParseException e) { 195 throw Util.toSAXParseException(e); 196 } 197 catch (XNIException e) { 198 throw Util.toSAXException(e); 199 } 200 finally { 201 // Release references to application objects 202 fRoot = null; 203 //fCurrentElement = null; -- keep the reference to support current-element-node property 204 fEntities = null; 205 if (fDOMValidatorHandler != null) { 206 fDOMValidatorHandler.setDOMResult(null); 207 } 208 } 209 } 210 return; 211 } 212 throw new IllegalArgumentException(JAXPValidationMessageFormatter.formatMessage(fComponentManager.getLocale(), 213 "SourceResultMismatch", 214 new Object [] {source.getClass().getName(), result.getClass().getName()})); 215 } 216 217 /* 218 * EntityState methods 219 */ 220 221 public boolean isEntityDeclared(String name) { 222 return false; 223 } 224 225 public boolean isEntityUnparsed(String name) { 226 if (fEntities != null) { 227 Entity entity = (Entity) fEntities.getNamedItem(name); 228 if (entity != null) { 229 return (entity.getNotationName() != null); 230 } 231 } 232 return false; 233 } 234 235 /* 236 * Other methods 237 */ 238 239 /** Traverse the DOM and fire events to the schema validator. */ 240 private void validate(Node node) { 241 final Node top = node; 242 // Performs a non-recursive traversal of the DOM. This 243 // will avoid a stack overflow for DOMs with high depth. 244 while (node != null) { 245 beginNode(node); 246 Node next = node.getFirstChild(); 247 while (next == null) { 248 finishNode(node); 249 if (top == node) { 250 break; 251 } 252 next = node.getNextSibling(); 253 if (next == null) { 254 node = node.getParentNode(); 255 if (node == null || top == node) { 256 if (node != null) { 257 finishNode(node); 258 } 259 next = null; 260 break; 261 } 262 } 263 } 264 node = next; 265 } 266 } 267 268 /** Do processing for the start of a node. */ 269 private void beginNode(Node node) { 270 switch (node.getNodeType()) { 271 case Node.ELEMENT_NODE: 272 fCurrentElement = node; 273 // push namespace context 274 fNamespaceContext.pushContext(); 275 // start element 276 fillQName(fElementQName, node); 277 processAttributes(node.getAttributes()); 278 fSchemaValidator.startElement(fElementQName, fAttributes, null); 279 break; 280 case Node.TEXT_NODE: 281 if (fDOMValidatorHandler != null) { 282 fDOMValidatorHandler.setIgnoringCharacters(true); 283 sendCharactersToValidator(node.getNodeValue()); 284 fDOMValidatorHandler.setIgnoringCharacters(false); 285 fDOMValidatorHandler.characters((Text) node); 286 } 287 else { 288 sendCharactersToValidator(node.getNodeValue()); 289 } 290 break; 291 case Node.CDATA_SECTION_NODE: 292 if (fDOMValidatorHandler != null) { 293 fDOMValidatorHandler.setIgnoringCharacters(true); 294 fSchemaValidator.startCDATA(null); 295 sendCharactersToValidator(node.getNodeValue()); 296 fSchemaValidator.endCDATA(null); 297 fDOMValidatorHandler.setIgnoringCharacters(false); 298 fDOMValidatorHandler.cdata((CDATASection) node); 299 } 300 else { 301 fSchemaValidator.startCDATA(null); 302 sendCharactersToValidator(node.getNodeValue()); 303 fSchemaValidator.endCDATA(null); 304 } 305 break; 306 case Node.PROCESSING_INSTRUCTION_NODE: 307 /** 308 * The validator does nothing with processing instructions so bypass it. 309 * Send the ProcessingInstruction node directly to the result builder. 310 */ 311 if (fDOMValidatorHandler != null) { 312 fDOMValidatorHandler.processingInstruction((ProcessingInstruction) node); 313 } 314 break; 315 case Node.COMMENT_NODE: 316 /** 317 * The validator does nothing with comments so bypass it. 318 * Send the Comment node directly to the result builder. 319 */ 320 if (fDOMValidatorHandler != null) { 321 fDOMValidatorHandler.comment((Comment) node); 322 } 323 break; 324 case Node.DOCUMENT_TYPE_NODE: 325 /** 326 * Send the DocumentType node directly to the result builder. 327 */ 328 if (fDOMValidatorHandler != null) { 329 fDOMValidatorHandler.doctypeDecl((DocumentType) node); 330 } 331 break; 332 default: // Ignore other node types. 333 break; 334 } 335 } 336 337 /** Do processing for the end of a node. */ 338 private void finishNode(Node node) { 339 if (node.getNodeType() == Node.ELEMENT_NODE) { 340 fCurrentElement = node; 341 // end element 342 fillQName(fElementQName, node); 343 fSchemaValidator.endElement(fElementQName, null); 344 // pop namespace context 345 fNamespaceContext.popContext(); 346 } 347 } 348 349 /** 350 * Extracts NamedNodeMap of entities. We need this to validate 351 * elements and attributes of type xs:ENTITY, xs:ENTITIES or 352 * types dervied from them. 353 */ 354 private void setupEntityMap(Document doc) { 355 if (doc != null) { 356 DocumentType docType = doc.getDoctype(); 357 if (docType != null) { 358 fEntities = docType.getEntities(); 359 return; 360 } 361 } 362 fEntities = null; 363 } 364 365 /** 366 * Sets up handler for <code>DOMResult</code>. 367 */ 368 private void setupDOMResultHandler(DOMSource source, DOMResult result) throws SAXException { 369 // If there's no DOMResult, unset the validator handler 370 if (result == null) { 371 fDOMValidatorHandler = null; 372 fSchemaValidator.setDocumentHandler(null); 373 return; 374 } 375 final Node nodeResult = result.getNode(); 376 // If the source node and result node are the same use the DOMResultAugmentor. 377 // Otherwise use the DOMResultBuilder. 378 if (source.getNode() == nodeResult) { 379 fDOMValidatorHandler = fDOMResultAugmentor; 380 fDOMResultAugmentor.setDOMResult(result); 381 fSchemaValidator.setDocumentHandler(fDOMResultAugmentor); 382 return; 383 } 384 if (result.getNode() == null) { 385 try { 386 DocumentBuilderFactory factory = JdkXmlUtils.getDOMFactory( 387 fComponentManager.getFeature(JdkXmlUtils.OVERRIDE_PARSER)); 388 DocumentBuilder builder = factory.newDocumentBuilder(); 389 result.setNode(builder.newDocument()); 390 } 391 catch (ParserConfigurationException e) { 392 throw new SAXException(e); 393 } 394 } 395 fDOMValidatorHandler = fDOMResultBuilder; 396 fDOMResultBuilder.setDOMResult(result); 397 fSchemaValidator.setDocumentHandler(fDOMResultBuilder); 398 } 399 400 private void fillQName(QName toFill, Node node) { 401 final String prefix = node.getPrefix(); 402 final String localName = node.getLocalName(); 403 final String rawName = node.getNodeName(); 404 final String namespace = node.getNamespaceURI(); 405 406 toFill.uri = (namespace != null && namespace.length() > 0) ? fSymbolTable.addSymbol(namespace) : null; 407 toFill.rawname = (rawName != null) ? fSymbolTable.addSymbol(rawName) : XMLSymbols.EMPTY_STRING; 408 409 // Is this a DOM level1 document? 410 if (localName == null) { 411 int k = rawName.indexOf(':'); 412 if (k > 0) { 413 toFill.prefix = fSymbolTable.addSymbol(rawName.substring(0, k)); 414 toFill.localpart = fSymbolTable.addSymbol(rawName.substring(k + 1)); 415 } 416 else { 417 toFill.prefix = XMLSymbols.EMPTY_STRING; 418 toFill.localpart = toFill.rawname; 419 } 420 } 421 else { 422 toFill.prefix = (prefix != null) ? fSymbolTable.addSymbol(prefix) : XMLSymbols.EMPTY_STRING; 423 toFill.localpart = (localName != null) ? fSymbolTable.addSymbol(localName) : XMLSymbols.EMPTY_STRING; 424 } 425 } 426 427 private void processAttributes(NamedNodeMap attrMap) { 428 final int attrCount = attrMap.getLength(); 429 fAttributes.removeAllAttributes(); 430 for (int i = 0; i < attrCount; ++i) { 431 Attr attr = (Attr) attrMap.item(i); 432 String value = attr.getValue(); 433 if (value == null) { 434 value = XMLSymbols.EMPTY_STRING; 435 } 436 fillQName(fAttributeQName, attr); 437 // REVISIT: Assuming all attributes are of type CDATA. The actual type may not matter. -- mrglavas 438 fAttributes.addAttributeNS(fAttributeQName, XMLSymbols.fCDATASymbol, value); 439 fAttributes.setSpecified(i, attr.getSpecified()); 440 // REVISIT: Should we be looking at non-namespace attributes 441 // for additional mappings? Should we detect illegal namespace 442 // declarations and exclude them from the context? -- mrglavas 443 if (fAttributeQName.uri == NamespaceContext.XMLNS_URI) { 444 // process namespace attribute 445 if (fAttributeQName.prefix == XMLSymbols.PREFIX_XMLNS) { 446 fNamespaceContext.declarePrefix(fAttributeQName.localpart, value.length() != 0 ? fSymbolTable.addSymbol(value) : null); 447 } 448 else { 449 fNamespaceContext.declarePrefix(XMLSymbols.EMPTY_STRING, value.length() != 0 ? fSymbolTable.addSymbol(value) : null); 450 } 451 } 452 } 453 } 454 455 private void sendCharactersToValidator(String str) { 456 if (str != null) { 457 final int length = str.length(); 458 final int remainder = length & CHUNK_MASK; 459 if (remainder > 0) { 460 str.getChars(0, remainder, fCharBuffer, 0); 461 fTempString.setValues(fCharBuffer, 0, remainder); 462 fSchemaValidator.characters(fTempString, null); 463 } 464 int i = remainder; 465 while (i < length) { 466 str.getChars(i, i += CHUNK_SIZE, fCharBuffer, 0); 467 fTempString.setValues(fCharBuffer, 0, CHUNK_SIZE); 468 fSchemaValidator.characters(fTempString, null); 469 } 470 } 471 } 472 473 Node getCurrentElement() { 474 return fCurrentElement; 475 } 476 477 /** 478 * NamespaceContext for the DOMSource, includes context for ancestor nodes. 479 */ 480 final class DOMNamespaceContext implements NamespaceContext { 481 482 // 483 // Data 484 // 485 486 /** 487 * Namespace binding information. This array is composed of a 488 * series of tuples containing the namespace binding information: 489 * <prefix, uri>. 490 */ 491 protected String[] fNamespace = new String[16 * 2]; 492 493 /** The size of the namespace information array. */ 494 protected int fNamespaceSize = 0; 495 496 /** 497 * Flag indicating whether the namespace context 498 * has been from the root node's ancestors. 499 */ 500 protected boolean fDOMContextBuilt = false; 501 502 // 503 // Methods 504 // 505 506 public void pushContext() { 507 fNamespaceContext.pushContext(); 508 } 509 510 public void popContext() { 511 fNamespaceContext.popContext(); 512 } 513 514 public boolean declarePrefix(String prefix, String uri) { 515 return fNamespaceContext.declarePrefix(prefix, uri); 516 } 517 518 public String getURI(String prefix) { 519 String uri = fNamespaceContext.getURI(prefix); 520 if (uri == null) { 521 if (!fDOMContextBuilt) { 522 fillNamespaceContext(); 523 fDOMContextBuilt = true; 524 } 525 if (fNamespaceSize > 0 && 526 !fNamespaceContext.containsPrefix(prefix)) { 527 uri = getURI0(prefix); 528 } 529 } 530 return uri; 531 } 532 533 public String getPrefix(String uri) { 534 return fNamespaceContext.getPrefix(uri); 535 } 536 537 public int getDeclaredPrefixCount() { 538 return fNamespaceContext.getDeclaredPrefixCount(); 539 } 540 541 public String getDeclaredPrefixAt(int index) { 542 return fNamespaceContext.getDeclaredPrefixAt(index); 543 } 544 545 public Enumeration getAllPrefixes() { 546 return fNamespaceContext.getAllPrefixes(); 547 } 548 549 public void reset() { 550 fDOMContextBuilt = false; 551 fNamespaceSize = 0; 552 } 553 554 private void fillNamespaceContext() { 555 if (fRoot != null) { 556 Node currentNode = fRoot.getParentNode(); 557 while (currentNode != null) { 558 if (Node.ELEMENT_NODE == currentNode.getNodeType()) { 559 NamedNodeMap attributes = currentNode.getAttributes(); 560 final int attrCount = attributes.getLength(); 561 for (int i = 0; i < attrCount; ++i) { 562 Attr attr = (Attr) attributes.item(i); 563 String value = attr.getValue(); 564 if (value == null) { 565 value = XMLSymbols.EMPTY_STRING; 566 } 567 fillQName(fAttributeQName, attr); 568 // REVISIT: Should we be looking at non-namespace attributes 569 // for additional mappings? Should we detect illegal namespace 570 // declarations and exclude them from the context? -- mrglavas 571 if (fAttributeQName.uri == NamespaceContext.XMLNS_URI) { 572 // process namespace attribute 573 if (fAttributeQName.prefix == XMLSymbols.PREFIX_XMLNS) { 574 declarePrefix0(fAttributeQName.localpart, value.length() != 0 ? fSymbolTable.addSymbol(value) : null); 575 } 576 else { 577 declarePrefix0(XMLSymbols.EMPTY_STRING, value.length() != 0 ? fSymbolTable.addSymbol(value) : null); 578 } 579 } 580 } 581 582 } 583 currentNode = currentNode.getParentNode(); 584 } 585 } 586 } 587 588 private void declarePrefix0(String prefix, String uri) { 589 // resize array, if needed 590 if (fNamespaceSize == fNamespace.length) { 591 String[] namespacearray = new String[fNamespaceSize * 2]; 592 System.arraycopy(fNamespace, 0, namespacearray, 0, fNamespaceSize); 593 fNamespace = namespacearray; 594 } 595 596 // bind prefix to uri in current context 597 fNamespace[fNamespaceSize++] = prefix; 598 fNamespace[fNamespaceSize++] = uri; 599 } 600 601 private String getURI0(String prefix) { 602 // find prefix in the DOM context 603 for (int i = 0; i < fNamespaceSize; i += 2) { 604 if (fNamespace[i] == prefix) { 605 return fNamespace[i + 1]; 606 } 607 } 608 // prefix not found 609 return null; 610 } 611 } 612 613 } // DOMValidatorHelper