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