| /************************************************************************ |
| * |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER |
| * |
| * Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved. |
| * |
| * Use is subject to license terms. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy |
| * of the License at http://www.apache.org/licenses/LICENSE-2.0. You can also |
| * obtain a copy of the License at http://odftoolkit.org/docs/license.txt |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| ************************************************************************/ |
| package org.odftoolkit.odfvalidator; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintStream; |
| import java.util.Iterator; |
| import java.util.Set; |
| import java.util.zip.ZipException; |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.parsers.SAXParserFactory; |
| import javax.xml.transform.sax.SAXSource; |
| import javax.xml.validation.Validator; |
| import org.odftoolkit.odfdom.doc.OdfDocument; |
| import org.odftoolkit.odfdom.pkg.OdfPackage; |
| import org.odftoolkit.odfdom.pkg.manifest.EncryptionDataElement; |
| import org.odftoolkit.odfdom.pkg.manifest.OdfFileEntry; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.XMLFilter; |
| import org.xml.sax.XMLReader; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| /** |
| * Validator for Files |
| */ |
| abstract class ODFPackageValidator { |
| |
| static final String DOCUMENT_SETTINGS = "document-settings"; |
| static final String DOCUMENT_STYLES = "document-styles"; |
| static final String DOCUMENT_CONTENT = "document-content"; |
| protected Logger.LogLevel m_nLogLevel; |
| protected OdfValidatorMode m_eMode = OdfValidatorMode.CONFORMANCE; |
| protected SAXParseExceptionFilter m_aFilter = null; |
| protected ODFValidatorProvider m_aValidatorProvider = null; |
| protected ODFValidationResult m_aResult = null; |
| protected OdfVersion m_aConfigVersion = null; |
| private SAXParserFactory m_aSAXParserFactory = null; |
| protected OdfVersion mOdfPackageVersion = null; |
| |
| protected ODFPackageValidator(Logger.LogLevel nLogLevel, OdfValidatorMode eMode, OdfVersion aVersion, |
| SAXParseExceptionFilter aFilter, ODFValidatorProvider aValidatorProvider) { |
| m_nLogLevel = nLogLevel; |
| m_eMode = eMode; |
| m_aFilter = aFilter; |
| m_aValidatorProvider = aValidatorProvider; |
| m_aConfigVersion = aVersion; |
| m_aResult = new ODFValidationResult(aVersion, eMode); |
| } |
| |
| protected abstract String getLoggerName(); |
| |
| protected abstract String getDocumentPath(); |
| |
| protected abstract OdfPackage getPackage(Logger aLogger); |
| |
| protected abstract String getStreamName(String aEntry); |
| |
| protected boolean validate(PrintStream aOut) throws ODFValidatorException { |
| Logger aLogger = |
| new Logger(getLoggerName(), getDocumentPath(), aOut, m_nLogLevel); |
| return _validate(aLogger); |
| } |
| |
| protected boolean validate(Logger aParentLogger) throws ODFValidatorException { |
| Logger aLogger = |
| new Logger(getDocumentPath(), aParentLogger); |
| return _validate(aLogger); |
| } |
| |
| |
| OdfVersion getOdfPackageVersion(){ |
| return mOdfPackageVersion; |
| } |
| |
| private boolean _validate(Logger aLogger) throws ODFValidatorException { |
| boolean bHasErrors = false; |
| |
| OdfPackage aPkg = getPackage(aLogger); |
| if (aPkg == null) { |
| return true; |
| } |
| |
| try { |
| String aDocVersion = getVersion(aLogger); |
| if (aDocVersion != null) { |
| aLogger.logInfo("ODF version of root document: " + aDocVersion, false); |
| mOdfPackageVersion = OdfVersion.valueOf(aDocVersion, true); |
| } |
| OdfVersion aVersion = m_aConfigVersion == null ? OdfVersion.valueOf(aDocVersion, true) : m_aConfigVersion; |
| |
| bHasErrors |= validatePre(aLogger, aVersion); |
| aLogger.logInfo("Media Type: " + m_aResult.getMediaType(), false); |
| |
| bHasErrors |= validateMeta(aLogger, getStreamName(OdfDocument.OdfXMLFile.META.getFileName()), aVersion, true); |
| bHasErrors |= validateEntry(aLogger, getStreamName(OdfDocument.OdfXMLFile.SETTINGS.getFileName()), DOCUMENT_SETTINGS, aVersion); |
| bHasErrors |= validateEntry(aLogger, getStreamName(OdfDocument.OdfXMLFile.STYLES.getFileName()), DOCUMENT_STYLES, aVersion); |
| if (m_aResult.getMediaType().equals(ODFMediaTypes.FORMULA_MEDIA_TYPE)) { |
| bHasErrors |= validateMathML(aLogger, getStreamName(OdfDocument.OdfXMLFile.CONTENT.getFileName()), aVersion); |
| } else { |
| bHasErrors |= validateEntry(aLogger, getStreamName(OdfDocument.OdfXMLFile.CONTENT.getFileName()), DOCUMENT_CONTENT, aVersion); |
| } |
| bHasErrors |= validatePost(aLogger, aVersion); |
| } catch (ZipException e) { |
| aLogger.logFatalError(e.getMessage()); |
| } catch (IOException e) { |
| aLogger.logFatalError(e.getMessage()); |
| } |
| |
| logSummary(bHasErrors, aLogger); |
| |
| return bHasErrors || aLogger.hasError(); |
| } |
| |
| protected boolean validatePre(Logger aLogger, OdfVersion aVersion) throws ODFValidatorException, IOException { |
| return false; |
| } |
| |
| protected boolean validatePost(Logger aLogger, OdfVersion aVersion) throws ODFValidatorException, IOException { |
| return false; |
| } |
| |
| protected void logSummary(boolean bHasErrors, Logger aLogger) { |
| } |
| |
| protected boolean validateEntry(Logger aParentLogger, String aEntryName, String aLocalElementName, OdfVersion aVersion) throws IOException, ZipException, IllegalStateException, ODFValidatorException { |
| Logger aLogger = new Logger(aEntryName, aParentLogger); |
| XMLFilter aFilter = new ContentFilter(aLogger, aLocalElementName); |
| if ((m_eMode == OdfValidatorMode.CONFORMANCE && aVersion.compareTo(OdfVersion.V1_1) <= 0) |
| || m_eMode == OdfValidatorMode.EXTENDED_CONFORMANCE) { |
| XMLFilter aAlienFilter = new ForeignContentFilter(aLogger, aVersion, m_aResult); |
| aAlienFilter.setParent(aFilter); |
| aFilter = aAlienFilter; |
| } |
| Validator aValidator = null; |
| if (m_eMode == OdfValidatorMode.VALIDATE_STRICT) { |
| aValidator = m_aValidatorProvider.getStrictValidator(aParentLogger.getOutputStream(), aVersion); |
| } else { |
| aValidator = m_aValidatorProvider.getValidator(aParentLogger.getOutputStream(), aVersion); |
| } |
| // Validator aValidator = m_eMode == OdfValidatorMode.VALIDATE_STRICT ? m_aValidatorProvider.getStrictValidator(aParentLogger.getOutputStream(), aVersion) |
| // : m_aValidatorProvider.getValidator(aParentLogger.getOutputStream(), aVersion); |
| return validateEntry(aFilter, aValidator, aLogger, aEntryName); |
| } |
| |
| private boolean validateMeta(Logger aParentLogger, String aEntryName, OdfVersion aVersion, boolean bIsRoot) throws IOException, ZipException, IllegalStateException, ODFValidatorException { |
| Logger aLogger = new Logger(aEntryName, aParentLogger); |
| XMLFilter aFilter = new MetaFilter(aLogger, m_aResult); |
| if ((m_eMode == OdfValidatorMode.CONFORMANCE && aVersion.compareTo(OdfVersion.V1_1) <= 0) |
| || m_eMode == OdfValidatorMode.EXTENDED_CONFORMANCE) { |
| XMLFilter aAlienFilter = new ForeignContentFilter(aLogger, aVersion, m_aResult); |
| aAlienFilter.setParent(aFilter); |
| aFilter = aAlienFilter; |
| } |
| |
| Validator aValidator = null; |
| if (m_eMode == OdfValidatorMode.VALIDATE_STRICT) { |
| aValidator = m_aValidatorProvider.getStrictValidator(aParentLogger.getOutputStream(), aVersion); |
| } else { |
| aValidator = m_aValidatorProvider.getValidator(aParentLogger.getOutputStream(), aVersion); |
| } |
| return validateEntry(aFilter, aValidator, aLogger, aEntryName); |
| } |
| |
| private boolean validateMathML(Logger aParentLogger, String aEntryName, OdfVersion aVersion) throws IOException, ZipException, IllegalStateException, ODFValidatorException { |
| Logger aLogger = new Logger(aEntryName, aParentLogger); |
| String aMathMLDTDSystemId = m_aValidatorProvider.getMathMLDTDSystemId(aVersion); |
| if (aMathMLDTDSystemId != null) { |
| // validate using DTD |
| return parseEntry(new MathML101Filter(aMathMLDTDSystemId, aLogger), aLogger, aEntryName, true); |
| } else { |
| Validator aMathMLValidator = m_aValidatorProvider.getMathMLValidator(aParentLogger.getOutputStream(), null); |
| if (aMathMLValidator == null) { |
| aLogger.logInfo("MathML schema is not available. Validation has been skipped.", false); |
| return false; |
| } |
| return validateEntry(new MathML20Filter(aLogger), aMathMLValidator, aLogger, aEntryName); |
| } |
| } |
| |
| protected boolean validateDSig(Logger aParentLogger, String aEntryName, OdfVersion aVersion) throws IOException, ZipException, IllegalStateException, ODFValidatorException { |
| Validator aValidator = m_aValidatorProvider.getDSigValidator(aParentLogger.getOutputStream(), aVersion); |
| Logger aLogger = new Logger(aEntryName, aParentLogger); |
| if (aValidator == null) { |
| aLogger.logWarning("Signature not validated because there is no Signature Validator configured for the selected Configuration"); |
| return false; |
| } |
| |
| return validateEntry(new DSigFilter(aLogger), aValidator, aLogger, aEntryName); |
| } |
| |
| protected boolean validateEntry(XMLFilter aFilter, |
| Validator aValidator, Logger aLogger, |
| String aEntryName) throws IOException, ZipException, IllegalStateException, ODFValidatorException { |
| OdfPackage aPkg = getPackage(aLogger); |
| |
| if (!aEntryName.equals(OdfPackage.OdfFile.MANIFEST.getPath()) && isEncrypted(aEntryName, aLogger)) { |
| return false; |
| } |
| |
| InputStream aInStream = null; |
| try { |
| aInStream = aPkg.getInputStream(aEntryName, true); |
| aLogger.setInputStream(aPkg.getInputStream(aEntryName, true)); |
| } catch (Exception e) { |
| throw new ODFValidatorException(e); |
| } |
| |
| |
| if (aValidator == null) { |
| |
| aLogger.logWarning("no Validator configured in selected Configuration for this file type"); |
| return false; |
| } |
| |
| return aInStream != null ? validate(aInStream, aFilter, aValidator, aLogger) : false; |
| } |
| |
| private boolean validate(InputStream aInStream, |
| XMLFilter aFilter, |
| javax.xml.validation.Validator aValidator, |
| Logger aLogger) throws ODFValidatorException { |
| SAXParser aParser = getSAXParser(false); |
| SchemaErrorHandler aErrorHandler = new SchemaErrorHandler(aLogger, m_aFilter); |
| |
| try { |
| XMLReader aReader; |
| if (aFilter != null) { |
| XMLReader aParent = aFilter.getParent(); |
| if (aParent != null) { |
| ((XMLFilter) aParent).setParent(aParser.getXMLReader()); |
| } else { |
| aFilter.setParent(aParser.getXMLReader()); |
| } |
| aReader = aFilter; |
| } else { |
| aReader = aParser.getXMLReader(); |
| } |
| |
| if (m_aFilter != null) { |
| m_aFilter.startSubFile(); |
| } |
| aValidator.setErrorHandler(aErrorHandler); |
| try { |
| aValidator.validate(new SAXSource(aReader, |
| new InputSource(aInStream))); |
| } catch (RuntimeException e) { |
| aLogger.logFatalError(e.getMessage()); |
| m_aValidatorProvider.resetValidatorProvider(); |
| } |
| } catch (org.xml.sax.SAXParseException e) { |
| aErrorHandler.fatalErrorNoException(e); |
| } catch (org.xml.sax.SAXException e) { |
| aLogger.logFatalError(e.getMessage()); |
| } catch (IOException e) { |
| aLogger.logFatalError(e.getMessage()); |
| } |
| |
| aLogger.logSummaryInfo(); |
| if (m_aResult.hasForeignElements()) { |
| Set<String> aForeignElementURISet = m_aResult.getForeignElements().keySet(); |
| StringBuilder aBuffer = new StringBuilder(); |
| Iterator<String> aIter = aForeignElementURISet.iterator(); |
| boolean bFirst = true; |
| while (aIter.hasNext()) { |
| String aURI = aIter.next(); |
| aBuffer.setLength(0); |
| aBuffer.append(m_aResult.getForeignElements().get(aURI)); |
| aBuffer.append(" extension elements from the following namespace were found: "); |
| aBuffer.append(aURI); |
| aLogger.logInfo(aBuffer.toString(), false); |
| } |
| } |
| if (m_aResult.hasForeignAttributes()) { |
| Set<String> aForeignAttributeURISet = m_aResult.getForeignAttributes().keySet(); |
| Iterator<String> aIter = aForeignAttributeURISet.iterator(); |
| StringBuilder aBuffer = new StringBuilder(); |
| while (aIter.hasNext()) { |
| String aURI = aIter.next(); |
| aBuffer.setLength(0); |
| aBuffer.append(m_aResult.getForeignAttributes().get(aURI)); |
| aBuffer.append(" extension attributes from the following namespace were found: "); |
| aBuffer.append(aURI); |
| aLogger.logInfo(aBuffer.toString(), false); |
| } |
| } |
| return aLogger.hasError(); |
| } |
| |
| protected boolean parseEntry(XMLFilter aFilter, |
| Logger aLogger, |
| String aEntryName, boolean bValidating) throws IOException, ZipException, IllegalStateException, ODFValidatorException { |
| OdfPackage aPkg = getPackage(aLogger); |
| |
| if (isEncrypted(aEntryName, aLogger)) { |
| return false; |
| } |
| |
| InputStream aInStream = null; |
| try { |
| aInStream = getPackage(aLogger).getInputStream(aEntryName, true); |
| } catch (Exception e) { |
| throw new ODFValidatorException(e); |
| } |
| |
| return aInStream != null ? parse(aInStream, aFilter, bValidating, aLogger) : false; |
| } |
| |
| private boolean parse(InputStream aInStream, XMLFilter aFilter, boolean bValidating, Logger aLogger) throws ODFValidatorException { |
| SAXParser aParser = getSAXParser(bValidating); |
| SchemaErrorHandler aErrorHandler = new SchemaErrorHandler(aLogger, m_aFilter); |
| |
| try { |
| XMLReader aReader; |
| if (aFilter != null) { |
| aFilter.setParent(aParser.getXMLReader()); |
| aReader = aFilter; |
| } else { |
| aReader = aParser.getXMLReader(); |
| } |
| if (m_aFilter != null) { |
| m_aFilter.startSubFile(); |
| } |
| aReader.setErrorHandler(aErrorHandler); |
| aReader.parse(new InputSource(aInStream)); |
| } catch (org.xml.sax.SAXParseException e) { |
| aErrorHandler.fatalErrorNoException(e); |
| } catch (org.xml.sax.SAXException e) { |
| aLogger.logFatalError(e.getMessage()); |
| } catch (IOException e) { |
| aLogger.logFatalError(e.getMessage()); |
| } |
| |
| if (bValidating) { |
| aLogger.logSummaryInfo(); |
| } |
| return aLogger.hasError(); |
| } |
| |
| private boolean isEncrypted(String aEntryName, Logger aLogger) { |
| OdfFileEntry aFileEntry = getPackage(aLogger).getFileEntry(aEntryName); |
| if (aFileEntry != null) { |
| EncryptionDataElement aEncData = aFileEntry.getEncryptionData(); |
| if (aEncData != null) { |
| aLogger.logFatalError("stream content is encrypted. Validation of encrypted content is not supported."); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private SAXParser getSAXParser(boolean bValidating) throws ODFValidatorException { |
| SAXParser aParser = null; |
| if (m_aSAXParserFactory == null) { |
| m_aSAXParserFactory = SAXParserFactory.newInstance(); |
| m_aSAXParserFactory.setNamespaceAware(true); |
| } |
| |
| try { |
| m_aSAXParserFactory.setValidating(bValidating); |
| aParser = m_aSAXParserFactory.newSAXParser(); |
| } catch (javax.xml.parsers.ParserConfigurationException e) { |
| throw new ODFValidatorException(e); |
| } catch (org.xml.sax.SAXException e) { |
| throw new ODFValidatorException(e); |
| } |
| |
| return aParser; |
| } |
| |
| /** |
| * get the generator |
| */ |
| public String getGenerator() { |
| return m_aResult.getGenerator(); |
| } |
| |
| private String getVersion(Logger aLogger) throws ODFValidatorException { |
| String aVersion = null; |
| |
| InputStream aInStream = null; |
| try { |
| OdfPackage aPkg = getPackage(aLogger); |
| aInStream = aPkg.getInputStream(getStreamName(OdfDocument.OdfXMLFile.META.getFileName()), true); |
| if (aInStream == null) { |
| aInStream = aPkg.getInputStream(getStreamName(OdfDocument.OdfXMLFile.SETTINGS.getFileName()), true); |
| } |
| if (aInStream == null) { |
| aInStream = aPkg.getInputStream(getStreamName(OdfDocument.OdfXMLFile.CONTENT.getFileName()), true); |
| } |
| if (aInStream == null) { |
| return null; |
| } |
| } catch (Exception e) { |
| aLogger.logFatalError(e.getMessage()); |
| } |
| |
| SAXParser aParser = getSAXParser(false); |
| |
| DefaultHandler aHandler = new VersionHandler(); |
| |
| try { |
| aParser.parse(aInStream, aHandler); |
| } catch (SAXVersionException e) { |
| aVersion = e.getVersion(); |
| } catch (org.xml.sax.SAXException e) { |
| aLogger.logFatalError(e.getMessage()); |
| } catch (IOException e) { |
| aLogger.logFatalError(e.getMessage()); |
| } |
| |
| return aVersion; |
| } |
| } |