/******************************************************************************* * Copyright (c) 2001, 2014 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Jens Lukowski/Innoopract - initial renaming/restructuring * *******************************************************************************/ package org.eclipse.wst.xml.core.internal.document; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; import org.eclipse.wst.xml.core.internal.XMLCoreMessages; import org.eclipse.wst.xml.core.internal.provisional.IXMLCharEntity; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; import org.w3c.dom.Attr; import org.w3c.dom.Node; /** */ public class SourceValidator { private NodeImpl node = null; /** */ public SourceValidator(Node node) { super(); if (node != null) { this.node = (NodeImpl) node; } } /** */ public String convertSource(String source) { if (source == null) return null; if (this.node == null) return null; // error // setup conversion conditions boolean acceptTag = false; boolean acceptClose = false; boolean acceptQuote = false; boolean acceptAmpersand = false; boolean acceptEntityRef = true; boolean acceptJSPEnd = true; String endTagName = null; if (this.node.getNodeType() == Node.ATTRIBUTE_NODE) { IDOMDocument document = (IDOMDocument) this.node.getOwnerDocument(); if (document != null && document.isJSPType()) acceptTag = true; if (acceptTag) { Attr attr = (Attr) this.node; ElementImpl element = (ElementImpl) attr.getOwnerElement(); if (element != null && element.isJSPTag()) acceptTag = false; } // if the source does not include single quote, // double quote is valid acceptQuote = (source.indexOf('\'') < 0); } else if (this.node.getNodeType() == Node.TEXT_NODE) { TextImpl text = (TextImpl) this.node; if (text.isJSPContent()) { int index = source.indexOf(JSPTag.TAG_CLOSE); if (index < 0) return source; acceptTag = true; acceptClose = true; acceptQuote = true; acceptAmpersand = true; acceptJSPEnd = false; } else if (text.isCDATAContent()) { endTagName = text.getParentNode().getNodeName(); if (endTagName == null) return null; // error acceptTag = true; acceptClose = true; acceptQuote = true; acceptAmpersand = true; } } else { IDOMDocument document = null; if (this.node.getNodeType() == Node.DOCUMENT_NODE) { document = (IDOMDocument) this.node; } else { document = (IDOMDocument) this.node.getOwnerDocument(); } if (document != null && document.isJSPType()) acceptTag = true; } StringBuffer buffer = null; int copiedLength = 0; int length = source.length(); for (int i = 0; i < length; i++) { String ref = null; char c = source.charAt(i); switch (c) { case '<' : if (acceptTag) { if (endTagName != null) { if (!matchEndTag(source, i + 1, endTagName)) continue; } else { int skip = skipTag(source, i + 1); if (skip >= 0) { i += skip; continue; } } // invalid JSP tag } ref = IXMLCharEntity.LT_REF; break; case '>' : if (acceptClose) continue; ref = IXMLCharEntity.GT_REF; break; case '&' : if (acceptAmpersand) continue; if (acceptEntityRef) { int skip = skipEntityRef(source, i + 1); if (skip >= 0) { i += skip; continue; } } ref = IXMLCharEntity.AMP_REF; break; case '"' : if (acceptQuote) continue; ref = IXMLCharEntity.QUOT_REF; break; case '%' : if (acceptJSPEnd) continue; if (source.charAt(i + 1) != '>') continue; i++; ref = IXMLCharEntity.GT_REF; break; default : continue; } if (ref != null) { if (buffer == null) { buffer = new StringBuffer(length + 8); } if (i > copiedLength) { buffer.append(source.substring(copiedLength, i)); } buffer.append(ref); copiedLength = i + 1; // skip this character } } if (buffer != null) { if (copiedLength < length) { buffer.append(source.substring(copiedLength, length)); } return buffer.toString(); } return source; } /** */ private final boolean matchEndTag(String source, int offset, String endTagName) { if (source == null || endTagName == null) return false; int length = source.length(); if (offset < 0 || offset >= length) return false; if (source.charAt(offset) != '/') return false; offset++; int end = offset + endTagName.length(); if (end > length) return false; return endTagName.equalsIgnoreCase(source.substring(offset, end)); } /** */ private final int skipEntityRef(String source, int offset) { if (source == null) return -1; if (offset < 0 || offset >= source.length()) return -1; DocumentImpl document = (DocumentImpl) this.node.getOwnerDocument(); if (document == null) return -1; // error int end = source.indexOf(';', offset); if (end < 0 || end == offset) return -1; String name = source.substring(offset, end); if (name == null || document.getCharValue(name) == null) return -1; return (end + 1 - offset); } /** */ private final int skipTag(String source, int offset) { if (source == null) return -1; if (offset < 0 || offset >= source.length()) return -1; int end = offset; if (source.charAt(offset) == '%') { // JSP tag int found = source.indexOf(JSPTag.TAG_CLOSE, offset + 1); if (found < 0) return -1; // invalid JSP tag end = found + 2; } else { // normal tag int found = source.lastIndexOf('>'); if (found < offset) return -1; // invalid tag end = found + 1; } return (end - offset); } /** */ public boolean validateSource(String source) throws InvalidCharacterException { if (source == null) return true; if (this.node == null) return false; // error String message = null; // setup validation conditions boolean acceptTag = false; boolean acceptClose = true; // See: Bug #432458 boolean acceptQuote = true; boolean acceptEntityRef = true; String endTagName = null; if (this.node.getNodeType() == Node.ATTRIBUTE_NODE) { IDOMDocument document = (IDOMDocument) this.node.getOwnerDocument(); if (document != null && document.isJSPType()) acceptTag = true; if (acceptTag) { Attr attr = (Attr) this.node; ElementImpl element = (ElementImpl) attr.getOwnerElement(); if (element != null && element.isJSPTag()) acceptTag = false; } // if the source does not include single quote, // double quote is valid acceptQuote = (source.indexOf('\'') < 0); } else if (this.node.getNodeType() == Node.TEXT_NODE) { TextImpl text = (TextImpl) this.node; if (text.isJSPContent()) { int index = source.indexOf(JSPTag.TAG_CLOSE); if (index < 0) return true; message = XMLCoreMessages.Invalid_character_gt_fo_ERROR_; throw new InvalidCharacterException(message, '>', index + 1); } else if (text.isCDATAContent()) { endTagName = text.getParentNode().getNodeName(); if (endTagName == null) return false; // error acceptTag = true; acceptClose = true; } else if(hasNestedRegion(text)) { //there are cases (such as with EL) that the text is to complicated // to be verified by this validator return true; } } else { IDOMDocument document = null; if (this.node.getNodeType() == Node.DOCUMENT_NODE) { document = (IDOMDocument) this.node; } else { document = (IDOMDocument) this.node.getOwnerDocument(); } if (document != null && document.isJSPType()) acceptTag = true; } char c = 0; int length = source.length(); for (int i = 0; i < length; i++) { c = source.charAt(i); switch (c) { case '<' : if (acceptTag) { if (endTagName != null) { if (!matchEndTag(source, i + 1, endTagName)) continue; } else { int skip = skipTag(source, i + 1); if (skip >= 0) { i += skip; continue; } } // invalid tag } message = XMLCoreMessages.Invalid_character_lt_fo_ERROR_; break; case '>' : if (acceptClose) continue; message = XMLCoreMessages.Invalid_character_gt_fo_ERROR_; break; case '&' : if (acceptEntityRef) { if (endTagName != null) continue; int skip = skipEntityRef(source, i + 1); if (skip >= 0) { i += skip; continue; } // invalid entity reference } message = XMLCoreMessages.Invalid_character_amp_fo_ERROR_; break; case '"' : if (acceptQuote) continue; message = XMLCoreMessages.Invalid_character__f_EXC_; break; default : continue; } if (message != null) { throw new InvalidCharacterException(message, c, i); } } return true; } /** * True if the text has nested regions, meaning container is probably too * complicated (like EL regions) to validate with this validator. * * @param text * @return */ private boolean hasNestedRegion(TextImpl text) { boolean done = false; IStructuredDocumentRegion currRegion = text.getFirstStructuredDocumentRegion(); IStructuredDocumentRegion lastRegion = text.getLastStructuredDocumentRegion(); while(currRegion != null && !done) { ITextRegionList regions = currRegion.getRegions(); for(int i = 0; i < regions.size(); ++i) { ITextRegion container = regions.get(i); if ((container instanceof ITextRegionContainer)) { ITextRegionList regions2 = ((ITextRegionContainer) container).getRegions(); if (regions2 != null) { return true; } } } done = currRegion == lastRegion; currRegion = currRegion.getNext(); } return false; } }