From odf-commits-return-763-apmail-incubator-odf-commits-archive=incubator.apache.org@incubator.apache.org Wed Jul 11 01:03:58 2012 Return-Path: X-Original-To: apmail-incubator-odf-commits-archive@minotaur.apache.org Delivered-To: apmail-incubator-odf-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 641BADBE8 for ; Wed, 11 Jul 2012 01:03:58 +0000 (UTC) Received: (qmail 91969 invoked by uid 500); 11 Jul 2012 01:03:58 -0000 Delivered-To: apmail-incubator-odf-commits-archive@incubator.apache.org Received: (qmail 91945 invoked by uid 500); 11 Jul 2012 01:03:58 -0000 Mailing-List: contact odf-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: odf-dev@incubator.apache.org Delivered-To: mailing list odf-commits@incubator.apache.org Received: (qmail 91936 invoked by uid 99); 11 Jul 2012 01:03:58 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 11 Jul 2012 01:03:58 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 11 Jul 2012 01:03:47 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id A026023888E4; Wed, 11 Jul 2012 01:03:25 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1359972 [2/3] - in /incubator/odf/trunk: odfdom/ odfdom/src/main/java/org/odftoolkit/odfdom/pkg/ odfdom/src/main/java/org/odftoolkit/odfdom/pkg/manifest/ odfdom/src/test/java/org/odftoolkit/odfdom/doc/ odfdom/src/test/java/org/odftoolkit/o... Date: Wed, 11 Jul 2012 01:03:24 -0000 To: odf-commits@incubator.apache.org From: robweir@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120711010325.A026023888E4@eris.apache.org> Modified: incubator/odf/trunk/odfdom/src/main/java/org/odftoolkit/odfdom/pkg/OdfPackage.java URL: http://svn.apache.org/viewvc/incubator/odf/trunk/odfdom/src/main/java/org/odftoolkit/odfdom/pkg/OdfPackage.java?rev=1359972&r1=1359971&r2=1359972&view=diff ============================================================================== --- incubator/odf/trunk/odfdom/src/main/java/org/odftoolkit/odfdom/pkg/OdfPackage.java (original) +++ incubator/odf/trunk/odfdom/src/main/java/org/odftoolkit/odfdom/pkg/OdfPackage.java Wed Jul 11 01:03:23 2012 @@ -37,6 +37,10 @@ import java.io.PipedOutputStream; import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.net.URI; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -44,31 +48,46 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeSet; +import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.Inflater; import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; + +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.URIResolver; + import org.apache.xerces.dom.DOMXSImplementationSourceImpl; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import org.odftoolkit.odfdom.doc.OdfDocument.OdfMediaType; import org.odftoolkit.odfdom.doc.OdfDocument; -import org.odftoolkit.odfdom.pkg.manifest.Algorithm; -import org.odftoolkit.odfdom.pkg.manifest.EncryptionData; -import org.odftoolkit.odfdom.pkg.manifest.KeyDerivation; +import org.odftoolkit.odfdom.doc.OdfDocument.OdfMediaType; +import org.odftoolkit.odfdom.pkg.manifest.AlgorithmElement; +import org.odftoolkit.odfdom.pkg.manifest.EncryptionDataElement; +import org.odftoolkit.odfdom.pkg.manifest.FileEntryElement; +import org.odftoolkit.odfdom.pkg.manifest.KeyDerivationElement; +import org.odftoolkit.odfdom.pkg.manifest.ManifestElement; import org.odftoolkit.odfdom.pkg.manifest.OdfFileEntry; +import org.odftoolkit.odfdom.pkg.manifest.OdfManifestDom; +import org.odftoolkit.odfdom.pkg.manifest.StartKeyGenerationElement; +import org.odftoolkit.odfdom.type.Base64Binary; import org.w3c.dom.Document; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSOutput; @@ -77,7 +96,6 @@ import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; /** @@ -112,23 +130,41 @@ public class OdfPackage implements Close private Map mZipEntries; private HashMap mOriginalZipEntries; private Map mManifestEntries; - // All opened documents from the same package are cached (including the root document) + // All opened documents from the same package are cached (including the root + // document) private Map mPkgDocuments; // Three different incarnations of a package file/data // save() will check 1) mPkgDoms, 2) if not check mMemoryFileCache private HashMap mPkgDoms; private HashMap mMemoryFileCache; + private ErrorHandler mErrorHandler; - private String mManifestVersion; + private String mManifestVersion; + private OdfManifestDom mManifestDom; + private String oldPwd; + private String newPwd; /* Commonly used files within the ODF Package */ public enum OdfFile { - /** The image directory is not defined by the OpenDocument standard, nevertheless the most spread ODF application OpenOffice.org is using the directory named "Pictures". */ + /** + * The image directory is not defined by the OpenDocument standard, + * nevertheless the most spread ODF application OpenOffice.org is using + * the directory named "Pictures". + */ IMAGE_DIRECTORY("Pictures"), - /** The "META-INF/manifest.xml" file is defined by the ODF 1.2 part 3 Package specification. This manifest is the 'content table' of the ODF package and describes the file entries of the ZIP including directories, but should not contain empty directories.*/ + /** + * The "META-INF/manifest.xml" file is defined by the ODF 1.2 part 3 + * Package specification. This manifest is the 'content table' of the + * ODF package and describes the file entries of the ZIP including + * directories, but should not contain empty directories. + */ MANIFEST("META-INF/manifest.xml"), - /** The "mimetype" file is defined by the ODF 1.2 part 3 Package specification. It contains the mediatype string of the root document and must be the first file in the ZIP and must not be compressed. */ + /** + * The "mimetype" file is defined by the ODF 1.2 part 3 Package + * specification. It contains the mediatype string of the root document + * and must be the first file in the ZIP and must not be compressed. + */ MEDIA_TYPE("mimetype"); private final String internalPath; @@ -143,9 +179,8 @@ public class OdfPackage implements Close static { mCompressedFileTypes = new HashSet(); - String[] typelist = new String[]{"jpg", "gif", "png", "zip", "rar", - "jpeg", "mpe", "mpg", "mpeg", "mpeg4", "mp4", "7z", "ari", - "arj", "jar", "gz", "tar", "war", "mov", "avi"}; + String[] typelist = new String[] { "jpg", "gif", "png", "zip", "rar", "jpeg", "mpe", "mpg", "mpeg", "mpeg4", "mp4", "7z", "ari", "arj", "jar", "gz", + "tar", "war", "mov", "avi" }; mCompressedFileTypes.addAll(Arrays.asList(typelist)); } @@ -159,21 +194,23 @@ public class OdfPackage implements Close mPkgDoms = new HashMap(); mMemoryFileCache = new HashMap(); mManifestEntries = new HashMap(); - // specify whether validation should be enabled and what SAX ErrorHandler should be used. + // specify whether validation should be enabled and what SAX + // ErrorHandler should be used. if (mErrorHandler == null) { String errorHandlerProperty = System.getProperty("org.odftoolkit.odfdom.validation"); if (errorHandlerProperty != null) { if (errorHandlerProperty.equalsIgnoreCase("true")) { mErrorHandler = new DefaultErrorHandler(); - Logger.getLogger(OdfPackage.class.getName()).fine("Activated validation with default ErrorHandler!"); + Logger.getLogger(OdfPackage.class.getName()).info("Activated validation with default ErrorHandler!"); } else { try { - Class cl = Class.forName(errorHandlerProperty); - Constructor ctor = cl.getDeclaredConstructor(new Class[]{}); + Class cl = Class.forName(errorHandlerProperty); + Constructor ctor = cl.getDeclaredConstructor(new Class[] {}); mErrorHandler = (ErrorHandler) ctor.newInstance(); - Logger.getLogger(OdfPackage.class.getName()).log(Level.FINE, "Activated validation with ErrorHandler:''{0}''!", errorHandlerProperty); + Logger.getLogger(OdfPackage.class.getName()).log(Level.CONFIG, "Activated validation with ErrorHandler:''{0}''!", errorHandlerProperty); } catch (Exception ex) { - Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, "Could not initiate validation with the given ErrorHandler: '" + errorHandlerProperty + "'", ex); + Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, + "Could not initiate validation with the given ErrorHandler: '" + errorHandlerProperty + "'", ex); } } } @@ -182,82 +219,73 @@ public class OdfPackage implements Close /** * Creates an OdfPackage from the OpenDocument provided by a File. - * + * *

* OdfPackage relies on the file being available for read access over the * whole lifecycle of OdfPackage. *

- * + * * @param pkgFile * - a file representing the ODF document - * @throws SAXException if there's an XML- or validation-related error while loading the package - * @throws IOException if there's an I/O error while loading the package - */ - private OdfPackage(File pkgFile) throws SAXException, IOException { - this(pkgFile, getBaseURLFromFile(pkgFile), null); - } - - /** - * Creates an OdfPackage from the OpenDocument provided by a File. - * - *

- * OdfPackage relies on the file being available for read access over the - * whole lifecycle of OdfPackage. - *

- * - * @param pkgFile a file representing the ODF document - * @param baseURI defining the base URI of ODF package. - * @param errorHandler - SAX ErrorHandler used for ODF validation - * @throws SAXException if there's an XML- or validation-related error while loading the package - * @throws IOException if there's an I/O error while loading the package - * + * @param baseURI + * defining the base URI of ODF package. + * @param password + * defining the password of ODF package. + * @param errorHandler + * - SAX ErrorHandler used for ODF validation + * @see #getErrorHandler + * @throws java.lang.Exception + * - if the package could not be created * @see #getErrorHandler* */ - private OdfPackage(File pkgFile, String baseURI, ErrorHandler errorHandler) throws SAXException, IOException { + private OdfPackage(File pkgFile, String baseURI, String password, ErrorHandler errorHandler) throws Exception { this(); - if (errorHandler != null) { - mErrorHandler = errorHandler; - } - mBaseURI = baseURI; - InputStream packageStream = new FileInputStream(pkgFile); - try { - initializeZip(packageStream); - } finally { - close(packageStream); - } + mBaseURI = getBaseURLFromFile(pkgFile); + mErrorHandler = errorHandler; + oldPwd = password; + newPwd = oldPwd; + initializeZip(pkgFile); } /** * Creates an OdfPackage from the OpenDocument provided by a InputStream. - * - *

Since an InputStream does not provide the arbitrary (non sequentiell) + * + *

+ * Since an InputStream does not provide the arbitrary (non sequentiell) * read access needed by OdfPackage, the InputStream is cached. This usually - * takes more time compared to the other constructors.

- * - * @param packageStream - an inputStream representing the ODF package - * @param baseURI defining the base URI of ODF package. - * @param errorHandler - SAX ErrorHandler used for ODF validation - * @throws SAXException if there's an XML- or validation-related error while loading the package - * @throws IOException if there's an I/O error while loading the package - * + * takes more time compared to the other constructors. + *

+ * + * @param packageStream + * - an inputStream representing the ODF package + * @param baseURI + * defining the base URI of ODF package. + * @param password + * defining the password of ODF package. + * @param errorHandler + * - SAX ErrorHandler used for ODF validation + * @see #getErrorHandler + * @throws java.lang.Exception + * - if the package could not be created * @see #getErrorHandler* */ - private OdfPackage(InputStream packageStream, String baseURI, ErrorHandler errorHandler) - throws SAXException, IOException { + private OdfPackage(InputStream packageStream, String baseURI, String password, ErrorHandler errorHandler) throws Exception { this(); // calling private constructor mErrorHandler = errorHandler; mBaseURI = baseURI; + oldPwd = password; + newPwd = oldPwd; initializeZip(packageStream); } /** * Loads an OdfPackage from the given documentURL. - * + * *

* OdfPackage relies on the file being available for read access over the * whole lifecycle of OdfPackage. *

- * + * * @param path * - the documentURL to the ODF package * @return the OpenDocument document represented as an OdfPackage @@ -266,83 +294,97 @@ public class OdfPackage implements Close */ public static OdfPackage loadPackage(String path) throws Exception { File pkgFile = new File(path); - return new OdfPackage(pkgFile, getBaseURLFromFile(pkgFile), null); + return new OdfPackage(pkgFile, getBaseURLFromFile(pkgFile), null, null); } /** * Loads an OdfPackage from the OpenDocument provided by a File. - * + * *

* OdfPackage relies on the file being available for read access over the * whole lifecycle of OdfPackage. *

- * - * @param pkgFile - the ODF Package + * + * @param pkgFile + * - the ODF Package * @return the OpenDocument document represented as an OdfPackage - * @throws java.lang.Exception - if the package could not be loaded + * @throws java.lang.Exception + * - if the package could not be loaded */ public static OdfPackage loadPackage(File pkgFile) throws Exception { - return new OdfPackage(pkgFile, getBaseURLFromFile(pkgFile), null); + return new OdfPackage(pkgFile, getBaseURLFromFile(pkgFile), null, null); } /** * Creates an OdfPackage from the given InputStream. - * + * *

* Since an InputStream does not provide the arbitrary (non sequentiell) * read access needed by OdfPackage, the InputStream is cached. This usually * takes more time compared to the other loadPackage methods. *

- * + * * @param packageStream * - an inputStream representing the ODF package * @return the OpenDocument document represented as an OdfPackage * @throws java.lang.Exception * - if the package could not be loaded */ - public static OdfPackage loadPackage(InputStream packageStream) - throws Exception { - return new OdfPackage(packageStream, null, null); + public static OdfPackage loadPackage(InputStream packageStream) throws Exception { + return new OdfPackage(packageStream, null, null, null); } /** * Creates an OdfPackage from the given InputStream. - * - *

OdfPackage relies on the file being available for read access over - * the whole lifecycle of OdfPackage.

- * - * @param packageStream - an inputStream representing the ODF package - * @param baseURI allows to explicitly set the base URI from the document, As the URL can not be derived from a stream. - * In addition it is possible to set the baseURI to any arbitrary URI, e.g. an URN. - * One usage of the baseURI to describe the source of validation exception thrown by the ErrorHandler. - * @param errorHandler - SAX ErrorHandler used for ODF validation - * @throws SAXException if there's an XML- or validation-related error while loading the package - * @throws IOException if there's an I/O error while loading the package + * + *

+ * OdfPackage relies on the file being available for read access over the + * whole lifecycle of OdfPackage. + *

+ * + * @param packageStream + * - an inputStream representing the ODF package + * @param baseURI + * allows to explicitly set the base URI from the document, As + * the URL can not be derived from a stream. In addition it is + * possible to set the baseURI to any arbitrary URI, e.g. an URN. + * One usage of the baseURI to describe the source of validation + * exception thrown by the ErrorHandler. + * @param errorHandler + * - SAX ErrorHandler used for ODF validation + * @throws java.lang.Exception + * - if the package could not be created * @see #getErrorHandler */ - public static OdfPackage loadPackage(InputStream packageStream, String baseURI, ErrorHandler errorHandler) - throws SAXException, IOException { - return new OdfPackage(packageStream, baseURI, errorHandler); + public static OdfPackage loadPackage(InputStream packageStream, String baseURI, ErrorHandler errorHandler) throws Exception { + return new OdfPackage(packageStream, baseURI, null, errorHandler); } /** * Loads an OdfPackage from the given File. - * - *

OdfPackage relies on the file being available for read access over - * the whole lifecycle of OdfPackage.

- * @param pkgFile - the ODF Package. A baseURL is being generated based on its location. - * @param errorHandler - SAX ErrorHandler used for ODF validation. - * @throws SAXException if there's an XML- or validation-related error while loading the package - * @throws IOException if there's an I/O error while loading the package + * + *

+ * OdfPackage relies on the file being available for read access over the + * whole lifecycle of OdfPackage. + *

+ * + * @param pkgFile + * - the ODF Package. A baseURL is being generated based on its + * location. + * @param password + * - the ODF Package password. + * @param errorHandler + * - SAX ErrorHandler used for ODF validation. + * @throws java.lang.Exception + * - if the package could not be created * @see #getErrorHandler */ - public static OdfPackage loadPackage(File pkgFile, ErrorHandler errorHandler) - throws SAXException, IOException { - return new OdfPackage(pkgFile, getBaseURLFromFile(pkgFile), errorHandler); + public static OdfPackage loadPackage(File pkgFile, String password, ErrorHandler errorHandler) throws Exception { + return new OdfPackage(pkgFile, getBaseURLFromFile(pkgFile), password, errorHandler); } // Initialize using memory - private void initializeZip(InputStream odfStream) throws SAXException, IOException { + private void initializeZip(InputStream odfStream) throws Exception { ByteArrayOutputStream tempBuf = new ByteArrayOutputStream(); StreamHelper.transformStream(odfStream, tempBuf); byte[] mTempByteBuf = tempBuf.toByteArray(); @@ -358,6 +400,20 @@ public class OdfPackage implements Close readZip(); } + // Initialize using ZipFile + private void initializeZip(File pkgFile) throws Exception { + try { + mZipFile = new ZipHelper(this, new ZipFile(pkgFile)); + } catch (ZipException ze) { + OdfValidationException ve = new OdfValidationException(OdfPackageConstraint.PACKAGE_IS_NO_ZIP, getBaseURI()); + if (mErrorHandler != null) { + mErrorHandler.fatalError(ve); + } + throw new IllegalArgumentException(ve); + } + readZip(); + } + private void readZip() throws SAXException, IOException { mZipEntries = new HashMap(); String firstEntryName = mZipFile.entriesToMap(mZipEntries); @@ -374,7 +430,8 @@ public class OdfPackage implements Close // initialize the package media type initializeMediaType(firstEntryName); - // ToDo: Remove all META-INF/* files from the fileEntries of Manifest + // ToDo: Remove all META-INF/* files from the fileEntries of + // Manifest mOriginalZipEntries = new HashMap(); mOriginalZipEntries.putAll(mZipEntries); mZipEntries.remove(OdfPackage.OdfFile.MEDIA_TYPE.getPath()); @@ -386,13 +443,14 @@ public class OdfPackage implements Close Iterator zipPaths = mZipEntries.keySet().iterator(); while (zipPaths.hasNext()) { String internalPath = zipPaths.next(); - // every resource aside the /META-INF/manifest.xml (and META-INF/ directory) + // every resource aside the /META-INF/manifest.xml (and + // META-INF/ directory) // and "mimetype" will be added as fileEntry - if (!internalPath.equals(OdfPackage.OdfFile.MANIFEST.getPath()) - && !internalPath.equals("META-INF/") + if (!internalPath.equals(OdfPackage.OdfFile.MANIFEST.getPath()) && !internalPath.equals("META-INF/") && !internalPath.equals(OdfPackage.OdfFile.MEDIA_TYPE.getPath())) { // aside "mediatype" and "META-INF/manifest" - // add manifest entry as to be described by a + // add manifest entry as to be described by a + // ensureFileEntryExistence(internalPath); } } @@ -400,17 +458,17 @@ public class OdfPackage implements Close } /** Validates if all file entries exist in the ZIP and vice versa */ - private void validateManifest() throws SAXException { - Set zipPaths = mZipEntries.keySet(); - Set manifestPaths = mManifestEntries.keySet(); + private void validateManifest() { + Set zipPaths = mZipEntries.keySet(); + Set manifestPaths = mManifestEntries.keySet(); Set sharedPaths = new HashSet(zipPaths); sharedPaths.retainAll(manifestPaths); if (sharedPaths.size() < zipPaths.size()) { Set zipPathSuperset = new HashSet(mZipEntries.keySet()); zipPathSuperset.removeAll(sharedPaths); - Set sortedSet = new TreeSet(zipPathSuperset); - Iterator iter = sortedSet.iterator(); + Set sortedSet = new TreeSet(zipPathSuperset); + Iterator iter = sortedSet.iterator(); String documentURL = getBaseURI(); String internalPath; while (iter.hasNext()) { @@ -426,7 +484,8 @@ public class OdfPackage implements Close // removing root directory zipPathSubset.remove(SLASH); - // No directory are listed in a ZIP removing all directory with content + // No directory are listed in a ZIP removing all directory with + // content Iterator manifestOnlyPaths = zipPathSubset.iterator(); while (manifestOnlyPaths.hasNext()) { String manifestOnlyPath = manifestOnlyPaths.next(); @@ -452,46 +511,61 @@ public class OdfPackage implements Close } /** Removes directories without a mimetype (all none documents) */ - private void removeDirectory(String path) throws SAXException { + private void removeDirectory(String path) { if (path.endsWith(SLASH)) { // Check if it is a sub-document? // Our assumption: it is a document if it has a mimetype... String dirMimeType = mManifestEntries.get(path).getMediaTypeString(); if (dirMimeType == null || EMPTY_STRING.equals(dirMimeType)) { logValidationWarning(OdfPackageConstraint.MANIFEST_LISTS_DIRECTORY, getBaseURI(), path); - mManifestEntries.remove(path); + OdfFileEntry manifestEntry = mManifestEntries.remove(path); + FileEntryElement manifestEle = manifestEntry.getOdfElement(); + manifestEle.getParentNode().removeChild(manifestEle); } } } - /** Reads the uncompressed "mimetype" file, which contains the package media/mimte type*/ - private void initializeMediaType(String firstEntryName) throws SAXException, IOException { + /** + * Reads the uncompressed "mimetype" file, which contains the package + * media/mimte type + */ + private void initializeMediaType(String firstEntryName) { ZipEntry mimetypeEntry = mZipEntries.get(OdfPackage.OdfFile.MEDIA_TYPE.getPath()); if (mimetypeEntry != null) { if (mErrorHandler != null) { validateMimeTypeEntry(mimetypeEntry, firstEntryName); } - // get mediatype value of the root document/package from the mediatype file stream + // get mediatype value of the root document/package from the + // mediatype file stream String entryMediaType = getMediaTypeFromEntry(mimetypeEntry); - // get mediatype value of the root document/package from the manifest.xml + // get mediatype value of the root document/package from the + // manifest.xml String manifestMediaType = getMediaTypeFromManifest(); // if a valid mediatype was set by the "mimetype" file if (entryMediaType != null && !entryMediaType.equals(EMPTY_STRING)) { - // the root document's mediatype is taken from the "mimetype" file + // the root document's mediatype is taken from the "mimetype" + // file mMediaType = entryMediaType; if (mErrorHandler != null) { - // if the "mediatype" does exist, the "/META-INF/manifest.xml" have to contain a MIMETYPE for the root document); + // if the "mediatype" does exist, the + // "/META-INF/manifest.xml" have to contain a MIMETYPE for + // the root document); if (manifestMediaType != null && !manifestMediaType.equals(EMPTY_STRING)) { // if the two media-types are inconsistent if (!entryMediaType.equals(manifestMediaType)) { - logValidationError(OdfPackageConstraint.MIMETYPE_DIFFERS_FROM_PACKAGE, getBaseURI(), CONTROL_CHAR_PATTERN.matcher(mMediaType).replaceAll(EMPTY_STRING), manifestMediaType); + logValidationError(OdfPackageConstraint.MIMETYPE_DIFFERS_FROM_PACKAGE, getBaseURI(), CONTROL_CHAR_PATTERN.matcher(mMediaType) + .replaceAll(EMPTY_STRING), manifestMediaType); } - } else { // if "mimetype" file exists, there have to be a mimetype in the manifest.xml for the root document (see ODF 1.2 part 3) - logValidationError(OdfPackageConstraint.MIMETYPE_WITHOUT_MANIFEST_MEDIATYPE, getBaseURI(), CONTROL_CHAR_PATTERN.matcher(mMediaType).replaceAll(EMPTY_STRING), manifestMediaType); + } else { // if "mimetype" file exists, there have to be a + // mimetype in the manifest.xml for the root + // document (see ODF 1.2 part 3) + logValidationError(OdfPackageConstraint.MIMETYPE_WITHOUT_MANIFEST_MEDIATYPE, getBaseURI(), CONTROL_CHAR_PATTERN.matcher(mMediaType) + .replaceAll(EMPTY_STRING), manifestMediaType); } } } else { // if there is no media-type was set by the "mimetype" file - // try as fall-back the mediatype of the root document from the manifest.xml + // try as fall-back the mediatype of the root document from the + // manifest.xml if (manifestMediaType != null && !manifestMediaType.equals(EMPTY_STRING)) { // and used as fall-back for the mediatype of the package mMediaType = manifestMediaType; @@ -500,7 +574,8 @@ public class OdfPackage implements Close } else { String manifestMediaType = getMediaTypeFromManifest(); if (manifestMediaType != null && !manifestMediaType.equals(EMPTY_STRING)) { - // if not mimetype file exists, the root document mediaType from the manifest.xml is taken + // if not mimetype file exists, the root document mediaType from + // the manifest.xml is taken mMediaType = manifestMediaType; } if (mErrorHandler != null) { @@ -509,7 +584,7 @@ public class OdfPackage implements Close } } - private void validateMimeTypeEntry(ZipEntry mimetypeEntry, String firstEntryName) throws SAXException { + private void validateMimeTypeEntry(ZipEntry mimetypeEntry, String firstEntryName) { if (mimetypeEntry.getMethod() != ZipEntry.STORED) { logValidationError(OdfPackageConstraint.MIMETYPE_IS_COMPRESSED, getBaseURI()); @@ -533,61 +608,37 @@ public class OdfPackage implements Close } /** @returns the media type of the root document from the manifest.xml */ - private String getMediaTypeFromEntry(ZipEntry mimetypeEntry) throws SAXException, IOException { + private String getMediaTypeFromEntry(ZipEntry mimetypeEntry) { String entryMediaType = null; ByteArrayOutputStream out = new ByteArrayOutputStream(); try { StreamHelper.transformStream(mZipFile.getInputStream(mimetypeEntry), out); entryMediaType = new String(out.toByteArray(), 0, out.size(), "UTF-8"); - } catch (IOException ex) { - handleIOException(ex, false); + } catch (Exception ex) { + Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex); } finally { - close(out); - } - return entryMediaType; - } - - private void close(Closeable closeable) throws SAXException, IOException { - if (closeable != null) { - try { - closeable.close(); - } catch (IOException ioe) { - //Warning only. This is usually just logged. - //Allow user to throw an exception all the same - handleIOException(ioe, true); - } - } - } - - private void handleIOException(IOException ex, boolean warningOnly) throws SAXException, IOException{ - if (mErrorHandler != null) { - SAXParseException se = new SAXParseException(ex.getMessage(), null, ex); - try { - if (warningOnly) { - mErrorHandler.warning(se); - } else { - mErrorHandler.error(se); - } - } catch (SAXException e1) { - if (e1 == se) { - throw ex; - //We re-throw the original exception if the error handler - //just threw the SAXException we gave it. - } else { - throw e1; //Throw what the error handler threw. + if (out != null) { + try { + out.close(); + } catch (IOException ex) { + Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex); } + out = null; } } - throw ex; //No error handler? Just throw the original IOException + return entryMediaType; } /** - * Insert an Odf document into the package at the given path. - * The path has to be a directory and will receive the MIME type of the OdfPackageDocument. - * - * @param doc the OdfPackageDocument to be inserted. + * Insert an Odf document into the package at the given path. The path has + * to be a directory and will receive the MIME type of the + * OdfPackageDocument. + * + * @param doc + * the OdfPackageDocument to be inserted. * @param internalPath - * path relative to the package root, where the document should be inserted. + * path relative to the package root, where the document should + * be inserted. */ void cacheDocument(OdfPackageDocument doc, String internalPath) { internalPath = normalizeDirectoryPath(internalPath); @@ -598,16 +649,19 @@ public class OdfPackage implements Close /** * Set the baseURI for this ODF package. NOTE: Should only be set during * saving the package. - * @param baseURI defining the location of the package + * + * @param baseURI + * defining the location of the package */ void setBaseURI(String baseURI) { mBaseURI = baseURI; } /** - * @return The URI to the ODF package, usually the URL, where this ODF package is located. - * If the package has not URI NULL is returned. - * This is the case if the package was new created without an URI and not saved before. + * @return The URI to the ODF package, usually the URL, where this ODF + * package is located. If the package has not URI NULL is returned. + * This is the case if the package was new created without an URI + * and not saved before. */ public String getBaseURI() { return mBaseURI; @@ -615,10 +669,12 @@ public class OdfPackage implements Close /** * Returns on ODF documents based a given mediatype. - * - * @param internalPath path relative to the package root, where the document should be inserted. + * + * @param internalPath + * path relative to the package root, where the document should + * be inserted. * @return The ODF document, which mediatype dependends on the parameter or - * NULL if media type were not supported. + * NULL if media type were not supported. */ public OdfPackageDocument loadDocument(String internalPath) { OdfPackageDocument doc = getCachedDocument(internalPath); @@ -635,11 +691,10 @@ public class OdfPackage implements Close if (odfMediaType == null) { return null; } - // ToDo: Issue 265 - Remove dependency to higher layer by factory + // ToDo: Issue 265 - Remove dependency to higher layer by + // facotory doc = OdfDocument.loadDocument(this, internalPath); } catch (Exception ex) { - // ToDo: catching Exception, logging it and continuing is bad style. - //Refactor exception handling in higher layer, too. Logger.getLogger(OdfPackageDocument.class.getName()).log(Level.SEVERE, null, ex); } } @@ -648,10 +703,12 @@ public class OdfPackage implements Close } /** - * @deprecated This method is only added temporary as workaround for the IBM fork using different DOC classes. - * Until the registering of DOC documents to the PKG layer has been finished. + * @deprecated This method is only added temporary as workaround for the IBM + * fork using different DOC classes. Until the registering of + * DOC documents to the PKG layer has been finished. * @param internalPath - * path relative to the package root, where the document should be inserted. + * path relative to the package root, where the document should + * be inserted. * @return an already open OdfPackageDocument via its path, otherwise NULL. */ @Deprecated @@ -662,9 +719,11 @@ public class OdfPackage implements Close /** * @param dom - * the DOM tree that has been parsed and should be added to the cache. + * the DOM tree that has been parsed and should be added to the + * cache. * @param internalPath - * path relative to the package root, where the XML of the DOM is located. + * path relative to the package root, where the XML of the DOM is + * located. * @return an already open OdfPackageDocument via its path, otherwise NULL. */ void cacheDom(Document dom, String internalPath) { @@ -674,7 +733,8 @@ public class OdfPackage implements Close /** * @param internalPath - * path relative to the package root, where the document should be inserted. + * path relative to the package root, where the document should + * be inserted. * @return an already open W3C XML Documenet via its path, otherwise NULL. */ Document getCachedDom(String internalPath) { @@ -683,44 +743,55 @@ public class OdfPackage implements Close } /** - * @return a map with all open W3C XML documents with their internal package path as key. + * @return a map with all open W3C XML documents with their internal package + * path as key. */ Map getCachedDoms() { return this.mPkgDoms; } /** - * Removes a document from the package via its path. Independent if it was already opened or not. + * Removes a document from the package via its path. Independent if it was + * already opened or not. + * * @param internalPath - * path relative to the package root, where the document should be removed. + * path relative to the package root, where the document should + * be removed. */ public void removeDocument(String internalPath) { // Note: the EMPTY String for rrot path will be exchanged to a SLASH internalPath = normalizeDirectoryPath(internalPath); - // get all files of the package - Set allPackageFileNames = getFilePaths(); + try { + // get all files of the package + Set allPackageFileNames = getFilePaths(); - // If the document is the root document - // the "/" representing the root document is outside the manifest.xml in the API an empty path - // still normalizeDirectoryPath() already exchanged the EMPTY_STRING to SLASH - if (internalPath.equals(SLASH)) { - for (String entryName : allPackageFileNames) { - remove(entryName); - } - remove(SLASH); - } else { - //remove all the stream of the directory, such as pictures - List directoryEntryNames = new ArrayList(); - for (String entryName : allPackageFileNames) { - if (entryName.startsWith(internalPath)) { - directoryEntryNames.add(entryName); + // If the document is the root document + // the "/" representing the root document is outside the + // manifest.xml in the API an empty path + // still normalizeDirectoryPath() already exchanged the EMPTY_STRING + // to SLASH + if (internalPath.equals(SLASH)) { + for (String entryName : allPackageFileNames) { + remove(entryName); } + remove(SLASH); + } else { + // remove all the stream of the directory, such as pictures + List directoryEntryNames = new ArrayList(); + for (String entryName : allPackageFileNames) { + if (entryName.startsWith(internalPath)) { + directoryEntryNames.add(entryName); + } + } + for (String entryName : directoryEntryNames) { + remove(entryName); + } + remove(internalPath); } - for (String entryName : directoryEntryNames) { - remove(entryName); - } - remove(internalPath); + } catch (Exception ex) { + Logger.getLogger(OdfPackageDocument.class.getName()).log(Level.SEVERE, null, ex); } + } /** @return all currently opened OdfPackageDocument of this OdfPackage */ @@ -732,11 +803,18 @@ public class OdfPackage implements Close return mPkgDocuments.get(OdfPackageDocument.ROOT_DOCUMENT_PATH); } + public OdfManifestDom getManifestDom() { + return mManifestDom; + } + /** - * Get the media type of the ODF file or document (ie. a directory). - * A directory with a mediatype can be loaded as OdfPackageDocument. - * Note: A directoy is represented by in the package as directory with media type - * @param internalPath within the package of the file or document. + * Get the media type of the ODF file or document (ie. a directory). A + * directory with a mediatype can be loaded as + * OdfPackageDocument. Note: A directoy is represented by in + * the package as directory with media type + * + * @param internalPath + * within the package of the file or document. * @return the mediaType for the resource of the given path */ public String getMediaTypeString(String internalPath) { @@ -768,7 +846,7 @@ public class OdfPackage implements Close /** * Get the media type of the ODF package (equal to media type of ODF root * document) - * + * * @return the mediaType string of this ODF package */ public String getMediaTypeString() { @@ -778,7 +856,7 @@ public class OdfPackage implements Close /** * Set the media type of the ODF package (equal to media type of ODF root * document) - * + * * @param mediaType * string of this ODF package */ @@ -787,10 +865,10 @@ public class OdfPackage implements Close } /** - * + * * Get an OdfFileEntry for the internalPath NOTE: This method should be * better moved to a DOM inherited Manifest class - * + * * @param internalPath * The relative package path within the ODF package * @return The manifest file entry will be returned. @@ -802,7 +880,7 @@ public class OdfPackage implements Close /** * Get a OdfFileEntries from the manifest file (i.e. /META/manifest.xml") - * + * * @return The paths of the manifest file entries will be returned. */ public Set getFilePaths() { @@ -810,9 +888,9 @@ public class OdfPackage implements Close } /** - * + * * Check existence of a file in the package. - * + * * @param internalPath * The relative package documentURL within the ODF package * @return True if there is an entry and a file for the given documentURL @@ -824,69 +902,77 @@ public class OdfPackage implements Close /** * Save the package to given documentURL. - * + * * @param odfPath * - the path to the ODF package destination - * @throws java.io.IOException + * @throws java.lang.Exception * - if the package could not be saved */ - public void save(String odfPath) throws IOException { + public void save(String odfPath) throws Exception { File f = new File(odfPath); save(f); } /** - * Save package to a given File. - * + * Save package to a given File. After saving it is still necessary to close + * the package to have again full access about the file. + * * @param pkgFile * - the File to save the ODF package to - * @throws java.lang.IOException + * @throws java.lang.Exception * - if the package could not be saved */ - public void save(File pkgFile) throws IOException { + public void save(File pkgFile) throws Exception { String baseURL = getBaseURLFromFile(pkgFile); -// if (baseURL.equals(mBaseURI)) { -// // save to the same file: cache everything first -// // ToDo: (Issue 219 - PackageRefactoring) --maybe it's better to write to a new file and copy that -// // to the original one - would be less memory footprint -// cacheContent(); -// } + // if (baseURL.equals(mBaseURI)) { + // // save to the same file: cache everything first + // // ToDo: (Issue 219 - PackageRefactoring) --maybe it's better to + // write to a new file and copy that + // // to the original one - would be less memory footprint + // cacheContent(); + // } FileOutputStream fos = new FileOutputStream(pkgFile); - try { - save(fos, baseURL); - } finally { - fos.close(); - } + save(fos, baseURL); + } + + public void save(OutputStream odfStream) throws Exception { + save(odfStream, null); } /** - * Saves the package to a given {@link OutputStream}. The given stream is not closed by this method. - * @param odfStream the output stream - * @throws IOException if an I/O error occurs while saving the package + * Sets the password of this package. if password is not null, package will + * be encrypted when save. + * + * @param password + * password + * @since 0.8.9 */ - public void save(OutputStream odfStream) throws IOException { - save(odfStream, null); + public void setPassword(String password) { + newPwd = password; } /** * Save an ODF document to the OutputStream. - * + * * @param odfStream * - the OutputStream to insert content to - * @param baseURL defining the location of the package - * @throws java.io.IOException if an I/O error occurs while saving the package + * @param baseURL + * defining the location of the package + * @throws java.lang.Exception + * - if the package could not be saved */ - private void save(OutputStream odfStream, String baseURL) throws IOException { - mBaseURI = baseURL; - OdfFileEntry rootEntry = mManifestEntries.get(SLASH); - if (rootEntry == null) { - rootEntry = new OdfFileEntry(SLASH, mMediaType); - mManifestEntries.put(SLASH, rootEntry); - } else { - rootEntry.setMediaTypeString(mMediaType); - } - ZipOutputStream zos = new ZipOutputStream(odfStream); + private void save(OutputStream odfStream, String baseURL) { try { + mBaseURI = baseURL; + OdfFileEntry rootEntry = mManifestEntries.get(SLASH); + if (rootEntry == null) { + rootEntry = new OdfFileEntry(getManifestDom().getRootElement().newFileEntryElement(SLASH, mMediaType)); + mManifestEntries.put(SLASH, rootEntry); + } else { + rootEntry.setMediaTypeString(mMediaType); + } + ZipOutputStream zos = new ZipOutputStream(odfStream); + // remove mediatype path and use it as first this.mManifestEntries.remove(OdfFile.MEDIA_TYPE.getPath()); Iterator it = mManifestEntries.keySet().iterator(); @@ -894,68 +980,122 @@ public class OdfPackage implements Close boolean isFirstFile = true; CRC32 crc = new CRC32(); long modTime = (new java.util.Date()).getTime(); + byte[] data = null; while (it.hasNext() || isFirstFile) { - byte[] data = null; - // ODF requires the "mimetype" file to be at first in the package - if (isFirstFile) { - isFirstFile = false; - // create "mimetype" from current attribute value - data = mMediaType.getBytes("UTF-8"); - createZipEntry(OdfFile.MEDIA_TYPE.getPath(), data, zos, modTime, crc); - // Create "META-INF/" directory - createZipEntry("META-INF/", null, zos, modTime, crc); - // Create "META-INF/manifest.xml" file - data = getBytes(OdfFile.MANIFEST.getPath()); - createZipEntry(OdfFile.MANIFEST.getPath(), data, zos, modTime, crc); - } else { - path = it.next(); - // not interested to reuse previous mediaType nor manifest from ZIP - if (!path.endsWith(SLASH) && !path.equals(OdfPackage.OdfFile.MANIFEST.getPath()) - && !path.equals(OdfPackage.OdfFile.MEDIA_TYPE.getPath())) { - data = getBytes(path); - createZipEntry(path, data, zos, modTime, crc); + try { + // ODF requires the "mimetype" file to be at first in the + // package + if (isFirstFile) { + isFirstFile = false; + // create "mimetype" from current attribute value + data = mMediaType.getBytes("UTF-8"); + createZipEntry(OdfFile.MEDIA_TYPE.getPath(), data, zos, modTime, crc); + } else { + path = it.next(); + // not interested to reuse previous mediaType nor + // manifest from ZIP + if (!path.endsWith(SLASH) && !path.equals(OdfPackage.OdfFile.MANIFEST.getPath()) + && !path.equals(OdfPackage.OdfFile.MEDIA_TYPE.getPath())) { + data = getBytes(path); + createZipEntry(path, data, zos, modTime, crc); + } } + data = null; + } catch (IOException ex) { + Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex); } } - } finally { + // Create "META-INF/" directory + createZipEntry("META-INF/", null, zos, modTime, crc); + // Create "META-INF/manifest.xml" file + data = getBytes(OdfFile.MANIFEST.getPath()); + createZipEntry(OdfFile.MANIFEST.getPath(), data, zos, modTime, crc); zos.close(); + odfStream.flush(); + } catch (IOException ex) { + Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex); } - odfStream.flush(); } - private void createZipEntry(String path, byte[] data, ZipOutputStream zos, long modTime, CRC32 crc) - throws IOException { - ZipEntry ze = mZipEntries.get(path); - if (ze == null) { - ze = new ZipEntry(path); - } - ze.setTime(modTime); - if (fileNeedsCompression(path)) { - ze.setMethod(ZipEntry.DEFLATED); - } else { - ze.setMethod(ZipEntry.STORED); + private void createZipEntry(String path, byte[] data, ZipOutputStream zos, long modTime, CRC32 crc) { + ZipEntry ze = null; + try { + ze = mZipEntries.get(path); + if (ze == null) { + ze = new ZipEntry(path); + } + ze.setTime(modTime); + if (fileNeedsCompression(path)) { + ze.setMethod(ZipEntry.DEFLATED); + } else { + ze.setMethod(ZipEntry.STORED); + } + crc.reset(); + if (data != null) { + OdfFileEntry fileEntry = mManifestEntries.get(path); + // encrypt file + if (data.length > 0 && fileNeedsEncryption(path)) { + data = encryptData(data, fileEntry); + // encrypted file entries shall be flagged as 'STORED'. + ze.setMethod(ZipEntry.STORED); + // the size of the encrypted file should replace the real + // size value. + ze.setCompressedSize(data.length); + } else { + if (fileEntry != null) { + fileEntry.setSize(null); + FileEntryElement fileEntryEle = fileEntry.getOdfElement(); + EncryptionDataElement encryptionDataElement = OdfElement.findFirstChildNode(EncryptionDataElement.class, fileEntryEle); + while (encryptionDataElement != null) { + fileEntryEle.removeChild(encryptionDataElement); + encryptionDataElement = OdfElement.findFirstChildNode(EncryptionDataElement.class, fileEntryEle); + } + } + ze.setCompressedSize(-1); + } + ze.setSize(data.length); + crc.update(data); + ze.setCrc(crc.getValue()); + } else { + ze.setSize(0); + ze.setCrc(0); + ze.setCompressedSize(-1); + } + zos.putNextEntry(ze); + if (data != null) { + zos.write(data, 0, data.length); + } + zos.closeEntry(); + mZipEntries.put(path, ze); + } catch (IOException ex) { + Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex); } - crc.reset(); - if (data != null) { - ze.setSize(data.length); - crc.update(data); - ze.setCrc(crc.getValue()); + } + + /** + * Determines if a file have to be encrypted. + * + * @param internalPath + * the file location + * @return true if the file needs encrypted, false, otherwise + */ + private boolean fileNeedsEncryption(String internalPath) { + if (newPwd != null) { + // ODF spec does not allow encrytion of "./mimetype" file + if (internalPath.endsWith(SLASH) || OdfFile.MANIFEST.getPath().equals(internalPath) || OdfPackage.OdfFile.MEDIA_TYPE.getPath().equals(internalPath)) { + return false; + } + return fileNeedsCompression(internalPath); } else { - ze.setSize(0); - ze.setCrc(0); - } - ze.setCompressedSize(-1); - zos.putNextEntry(ze); - if (data != null) { - zos.write(data, 0, data.length); + return false; } - zos.closeEntry(); - mZipEntries.put(path, ze); } /** * Determines if a file have to be compressed. - * @param internalPath the file location + * + * @param internalPath + * the file location * @return true if the file needs compression, false, otherwise */ private boolean fileNeedsCompression(String internalPath) { @@ -988,8 +1128,7 @@ public class OdfPackage implements Close mZipFile.close(); } catch (IOException ex) { // log exception and continue - Logger.getLogger(OdfPackage.class.getName()).log(Level.INFO, - null, ex); + Logger.getLogger(OdfPackage.class.getName()).log(Level.INFO, null, ex); } } // release all stuff - this class is impossible to use afterwards @@ -1006,36 +1145,46 @@ public class OdfPackage implements Close /** * Parse the Manifest file */ - private void parseManifest() throws SAXException, IOException { - InputStream is = null; + + private void parseManifest() { + // ToDo: manifest.xml will be held in the future as DOM, now its + // being generated for each save() try { - ZipEntry entry = null; - // loading the MANIFEST once from the ZIP, as it will never be cached, just once read - // during load (now) and on save serialized from file status (ie. mManifestEntries) - if ((entry = mZipEntries.get(OdfPackage.OdfFile.MANIFEST.internalPath)) != null) { - is = mZipFile.getInputStream(entry); - } - if (is == null) { + mManifestDom = (OdfManifestDom) OdfFileDom.newFileDom(this, OdfFile.MANIFEST.getPath()); + ManifestElement manifestEle = mManifestDom.getRootElement(); + if (manifestEle != null) { + setManifestVersion(manifestEle.getVersionAttribute()); + } else { logValidationError(OdfPackageConstraint.MANIFEST_NOT_IN_PACKAGE, getBaseURI()); - return; } - XMLReader xmlReader = getXMLReader(); - xmlReader.setEntityResolver(getEntityResolver()); - xmlReader.setContentHandler(new OdfManifestSaxHandler(this)); - InputSource ins = new InputSource(is); - String uri = mBaseURI + SLASH + OdfPackage.OdfFile.MANIFEST.internalPath; - ins.setSystemId(uri); - xmlReader.parse(ins); - // ToDo: manifest.xml will be held in the future as DOM, now its being generated for each save() - mMemoryFileCache.remove(OdfPackage.OdfFile.MANIFEST.internalPath); - } catch (IOException ioe) { - handleIOException(ioe, false); - } finally { - close(is); + Map entries = getManifestEntries(); + FileEntryElement fileEntryEle = OdfElement.findFirstChildNode(FileEntryElement.class, manifestEle); + while (fileEntryEle != null) { + String path = fileEntryEle.getFullPathAttribute(); + if (path.equals(EMPTY_STRING)) { + if (getErrorHandler() != null) { + logValidationError(OdfPackageConstraint.MANIFEST_WITH_EMPTY_PATH, getBaseURI()); + } + } + path = normalizePath(path); + OdfFileEntry currentFileEntry = entries.get(path); + if (currentFileEntry == null) { + currentFileEntry = new OdfFileEntry(fileEntryEle); + } + if (path != null) { + entries.put(path, currentFileEntry); + } + fileEntryEle = OdfElement.findNextChildNode(FileEntryElement.class, fileEntryEle); + } + mMemoryFileCache.remove(OdfFile.MANIFEST.getPath()); + mPkgDoms.put(OdfFile.MANIFEST.getPath(), mManifestDom); + } catch (Exception ex) { + Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex); } } - XMLReader getXMLReader() throws SAXException { + + XMLReader getXMLReader() throws ParserConfigurationException, SAXException { // create sax parser SAXParserFactory saxFactory = new org.apache.xerces.jaxp.SAXParserFactoryImpl(); saxFactory.setNamespaceAware(true); @@ -1046,19 +1195,16 @@ public class OdfPackage implements Close Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex); } - SAXParser parser; - try { - parser = saxFactory.newSAXParser(); - } catch (ParserConfigurationException pce) { - //Re-throw as SAXException in order not to introduce too many checked exceptions - throw new SAXException(pce); - } + SAXParser parser = saxFactory.newSAXParser(); XMLReader xmlReader = parser.getXMLReader(); - // More details at http://xerces.apache.org/xerces2-j/features.html#namespaces + // More details at + // http://xerces.apache.org/xerces2-j/features.html#namespaces xmlReader.setFeature("http://xml.org/sax/features/namespaces", true); - // More details at http://xerces.apache.org/xerces2-j/features.html#namespace-prefixes + // More details at + // http://xerces.apache.org/xerces2-j/features.html#namespace-prefixes xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true); - // More details at http://xerces.apache.org/xerces2-j/features.html#xmlns-uris + // More details at + // http://xerces.apache.org/xerces2-j/features.html#xmlns-uris xmlReader.setFeature("http://xml.org/sax/features/xmlns-uris", true); return xmlReader; } @@ -1066,6 +1212,7 @@ public class OdfPackage implements Close // Add the given path and all its subdirectories to the internalPath list // to be written later to the manifest private void createSubEntries(String internalPath) { + ManifestElement manifestEle = getManifestDom().getRootElement(); StringTokenizer tok = new StringTokenizer(internalPath, SLASH); if (tok.countTokens() > 1) { String path = EMPTY_STRING; @@ -1076,7 +1223,7 @@ public class OdfPackage implements Close path = path + directory + SLASH; OdfFileEntry fileEntry = mManifestEntries.get(path); if (fileEntry == null) { - mManifestEntries.put(path, new OdfFileEntry(path, null)); + mManifestEntries.put(path, new OdfFileEntry(manifestEle.newFileEntryElement(path, null))); } } } @@ -1085,11 +1232,12 @@ public class OdfPackage implements Close /** * Insert DOM tree into OdfPackage. An existing file will be replaced. - * + * * @param fileDOM * - XML DOM tree to be inserted as file. * @param internalPath - * - relative documentURL where the DOM tree should be inserted as XML file + * - relative documentURL where the DOM tree should be inserted + * as XML file * @param mediaType * - media type of stream. Set to null if unknown * @throws java.lang.Exception @@ -1105,35 +1253,42 @@ public class OdfPackage implements Close } else { mPkgDoms.put(internalPath, fileDOM); } - updateFileEntry(ensureFileEntryExistence(internalPath), mediaType); + if (!internalPath.endsWith(OdfFile.MANIFEST.internalPath)) { + updateFileEntry(ensureFileEntryExistence(internalPath), mediaType); + } // remove byte array version of new DOM mMemoryFileCache.remove(internalPath); } /** - * Embed an OdfPackageDocument to the current OdfPackage. - * All the file entries of child document will be inserted. - * @param sourceDocument the OdfPackageDocument to be embedded. - * @param internalPath path to the directory the ODF document should be inserted (relative to ODF package root). + * Embed an OdfPackageDocument to the current OdfPackage. All the file + * entries of child document will be inserted. + * + * @param sourceDocument + * the OdfPackageDocument to be embedded. + * @param internalPath + * path to the directory the ODF document should be inserted + * (relative to ODF package root). */ public void insertDocument(OdfPackageDocument sourceDocument, String internalPath) { internalPath = normalizeDirectoryPath(internalPath); // opened DOM of descendant Documents will be flashed to the their pkg flushDoms(sourceDocument); - // Gets the OdfDocument's manifest entry info, no matter it is a independent document or an embeddedDocument. + // Gets the OdfDocument's manifest entry info, no matter it is a + // independent document or an embeddedDocument. Map entryMapToCopy; if (sourceDocument.isRootDocument()) { entryMapToCopy = sourceDocument.getPackage().getManifestEntries(); } else { - entryMapToCopy = sourceDocument.getPackage().getSubDirectoryEntries(sourceDocument.getDocumentPath()); + entryMapToCopy = sourceDocument.getPackage().getSubDirectoryEntries(this, sourceDocument.getDocumentPath()); } - //insert to package and add it to the Manifest + // insert to package and add it to the Manifest internalPath = sourceDocument.setDocumentPath(internalPath); String documentDirectory = null; - if(internalPath.equals(SLASH)){ + if (internalPath.equals(SLASH)) { documentDirectory = EMPTY_STRING; - }else{ + } else { documentDirectory = internalPath; } Set entryNameList = entryMapToCopy.keySet(); @@ -1153,16 +1308,17 @@ public class OdfPackage implements Close String packagePath = documentDirectory + entry.getPath(); insert(sourceDocument.getPackage().getInputStream(entryName), packagePath, entry.getMediaTypeString()); } - } catch (IOException ex) { - //Catching IOException should be fine here: in-memory operations only + } catch (Exception ex) { Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex); } } } - //make sure the media type of embedded Document is right set. - OdfFileEntry embedDocumentRootEntry = new OdfFileEntry(internalPath, sourceDocument.getMediaTypeString()); + // make sure the media type of embedded Document is right set. + ManifestElement manifestEle = mManifestDom.getRootElement(); + OdfFileEntry embedDocumentRootEntry = new OdfFileEntry(manifestEle.newFileEntryElement(internalPath, sourceDocument.getMediaTypeString())); mManifestEntries.put(internalPath, embedDocumentRootEntry); - // the new document will be attached to its new package (it has been inserted to) + // the new document will be attached to its new package (it has been + // inserted to) sourceDocument.setPackage(this); cacheDocument(sourceDocument, internalPath); } @@ -1170,15 +1326,17 @@ public class OdfPackage implements Close /** * Insert all open DOMs of XML files beyond parent document to the package. * The XML files will be updated in the package after calling save. - * - * @param parentDocument the document, which XML files shall be serialized + * + * @param parentDocument + * the document, which XML files shall be serialized */ void flushDoms(OdfPackageDocument parentDocument) { OdfPackage pkg = parentDocument.getPackage(); if (parentDocument.isRootDocument()) { // for every parsed XML file (DOM) for (String xmlFilePath : pkg.getCachedDoms().keySet()) { - // insert it to the package (serializing and caching it till final save) + // insert it to the package (serializing and caching it till + // final save) pkg.insert(pkg.getCachedDom(xmlFilePath), xmlFilePath, "text/xml"); } } else { @@ -1188,7 +1346,8 @@ public class OdfPackage implements Close for (String xmlFilePath : pkg.getCachedDoms().keySet()) { // if the file is within the given document if (xmlFilePath.startsWith(parentDocumentPath)) { - // insert it to the package (serializing and caching it till final save) + // insert it to the package (serializing and caching it till + // final save) pkg.insert(pkg.getCachedDom(xmlFilePath), xmlFilePath, "text/xml"); } } @@ -1196,22 +1355,21 @@ public class OdfPackage implements Close } /** Get all the file entries from a sub directory */ - private Map getSubDirectoryEntries(String directory) { + private Map getSubDirectoryEntries(OdfPackage destinationPackage, String directory) { directory = normalizeDirectoryPath(directory); Map subEntries = new HashMap(); Map allEntries = getManifestEntries(); Set rootEntryNameSet = getFilePaths(); + ManifestElement manifestEle = destinationPackage.getManifestDom().getRootElement(); for (String entryName : rootEntryNameSet) { if (entryName.startsWith(directory)) { String newEntryName = entryName.substring(directory.length()); if (newEntryName.length() == 0) { - continue; + newEntryName = SLASH; } OdfFileEntry srcFileEntry = allEntries.get(entryName); - OdfFileEntry newFileEntry = new OdfFileEntry(); + OdfFileEntry newFileEntry = new OdfFileEntry(manifestEle.newFileEntryElement(newEntryName, srcFileEntry.getMediaTypeString())); newFileEntry.setEncryptionData(srcFileEntry.getEncryptionData()); - newFileEntry.setMediaTypeString(srcFileEntry.getMediaTypeString()); - newFileEntry.setPath(newEntryName); newFileEntry.setSize(srcFileEntry.getSize()); subEntries.put(entryName, newFileEntry); } @@ -1221,42 +1379,56 @@ public class OdfPackage implements Close /** * Method returns the paths of all document within the package. - * - * @return A set of paths of all documents of the package, including the root document. + * + * @return A set of paths of all documents of the package, including the + * root document. */ public Set getDocumentPaths() { return getDocumentPaths(null, null); } /** - * Method returns the paths of all document within the package matching the given criteria. - * - * @param mediaTypeString limits the desired set of document paths to documents of the given mediaType - * @return A set of paths of all documents of the package, including the root document, that match the given parameter. + * Method returns the paths of all document within the package matching the + * given criteria. + * + * @param mediaTypeString + * limits the desired set of document paths to documents of the + * given mediaType + * @return A set of paths of all documents of the package, including the + * root document, that match the given parameter. */ public Set getDocumentPaths(String mediaTypeString) { return getDocumentPaths(mediaTypeString, null); } /** - * Method returns the paths of all document within the package matching the given criteria. - * - * @param mediaTypeString limits the desired set of document paths to documents of the given mediaType - * @param subDirectory limits the desired set document paths to those documents below of this subdirectory - * @return A set of paths of all documents of the package, including the root document, that match the given parameter. + * Method returns the paths of all document within the package matching the + * given criteria. + * + * @param mediaTypeString + * limits the desired set of document paths to documents of the + * given mediaType + * @param subDirectory + * limits the desired set document paths to those documents below + * of this subdirectory + * @return A set of paths of all documents of the package, including the + * root document, that match the given parameter. */ Set getDocumentPaths(String mediaTypeString, String subDirectory) { Set innerDocuments = new HashSet(); Set packageFilePaths = getFilePaths(); // check manifest for current embedded OdfPackageDocuments for (String filePath : packageFilePaths) { - // check if a subdirectory was the criteria and if the files are beyond the given subdirectory + // check if a subdirectory was the criteria and if the files are + // beyond the given subdirectory if (subDirectory == null || filePath.startsWith(subDirectory) && !filePath.equals(subDirectory)) { - // with documentURL is not empty and is a directory (ie. a potential document) + // with documentURL is not empty and is a directory (ie. a + // potential document) if (filePath.length() > 1 && filePath.endsWith(SLASH)) { String fileMediaType = getFileEntry(filePath).getMediaTypeString(); if (fileMediaType != null && !fileMediaType.equals(EMPTY_STRING)) { - // check if a certain mediaType was the critera and was matched + // check if a certain mediaType was the critera and was + // matched if (mediaTypeString == null || mediaTypeString.equals(fileMediaType)) { // only relative path is allowed as path innerDocuments.add(filePath); @@ -1269,21 +1441,21 @@ public class OdfPackage implements Close } /** - * Adding a manifest:file-entry to be saved in manifest.xml. - * In addition, sub directories will be added as well to the manifest. + * Adding a manifest:file-entry to be saved in manifest.xml. In addition, + * sub directories will be added as well to the manifest. */ private OdfFileEntry ensureFileEntryExistence(String internalPath) { // if it is NOT the resource "/META-INF/manifest.xml" OdfFileEntry fileEntry = null; - if (!OdfPackage.OdfFile.MANIFEST.internalPath.equals(internalPath) || - !internalPath.equals(EMPTY_STRING)) { + if (!OdfFile.MANIFEST.internalPath.equals(internalPath) || !internalPath.equals(EMPTY_STRING)) { if (mManifestEntries == null) { mManifestEntries = new HashMap(); } fileEntry = mManifestEntries.get(internalPath); // for every new file entry if (fileEntry == null) { - fileEntry = new OdfFileEntry(internalPath); + ManifestElement manifestEle = getManifestDom().getRootElement(); + fileEntry = new OdfFileEntry(manifestEle.newFileEntryElement(internalPath, "")); mManifestEntries.put(internalPath, fileEntry); // creates recursive file entries for all sub directories createSubEntries(internalPath); @@ -1301,13 +1473,14 @@ public class OdfPackage implements Close // reset encryption data (ODFDOM does not support encryption yet) fileEntry.setEncryptionData(null); // reset size to be unset - fileEntry.setSize(-1); + fileEntry.setSize(null); } /** * Gets org.w3c.dom.Document for XML file contained in package. - * - * @param internalPath to a file within the Odf Package (eg. content.xml) + * + * @param internalPath + * to a file within the Odf Package (eg. content.xml) * @return an org.w3c.dom.Document * @throws SAXException * @throws ParserConfigurationException @@ -1316,9 +1489,8 @@ public class OdfPackage implements Close * @throws TransformerConfigurationException * @throws TransformerException */ - public Document getDom(String internalPath) throws SAXException, - ParserConfigurationException, IllegalArgumentException, - TransformerConfigurationException, TransformerException, IOException { + public Document getDom(String internalPath) throws SAXException, ParserConfigurationException, IllegalArgumentException, TransformerConfigurationException, + TransformerException, IOException { Document doc = mPkgDoms.get(internalPath); if (doc != null) { @@ -1358,19 +1530,18 @@ public class OdfPackage implements Close /** * Inserts an external file into an OdfPackage. An existing file will be * replaced. - * + * * @param sourceURI * - the source URI to the file to be inserted into the package. * @param internalPath - * - relative documentURL where the tree should be inserted as XML - * file + * - relative documentURL where the tree should be inserted as + * XML file * @param mediaType * - media type of stream. Set to null if unknown - * @throws java.io.IOException + * @throws java.lang.Exception * In case the file could not be saved */ - public void insert(URI sourceURI, String internalPath, String mediaType) - throws IOException { + public void insert(URI sourceURI, String internalPath, String mediaType) throws Exception { InputStream is = null; if (sourceURI.isAbsolute()) { // if the URI is absolute it can be converted to URL @@ -1385,19 +1556,19 @@ public class OdfPackage implements Close /** * Inserts InputStream into an OdfPackage. An existing file will be * replaced. - * + * * @param fileStream * - the stream of the file to be inserted into the ODF package. * @param internalPath - * - relative documentURL where the tree should be inserted as XML - * file + * - relative documentURL where the tree should be inserted as + * XML file * @param mediaType * - media type of stream. Set to null if unknown */ - public void insert(InputStream fileStream, String internalPath, String mediaType) throws IOException { + public void insert(InputStream fileStream, String internalPath, String mediaType) throws Exception { internalPath = normalizeFilePath(internalPath); if (fileStream == null) { - //adding a simple directory without MIMETYPE + // adding a simple directory without MIMETYPE insert((byte[]) null, internalPath, mediaType); } else { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -1415,15 +1586,16 @@ public class OdfPackage implements Close /** * Inserts a byte array into OdfPackage. An existing file will be replaced. - * If the byte array is NULL a directory with the given mimetype will be created. - * + * If the byte array is NULL a directory with the given mimetype will be + * created. + * * @param fileBytes - * - data of the file stream to be stored in package. - * If NULL a directory with the given mimetype will be created. + * - data of the file stream to be stored in package. If NULL a + * directory with the given mimetype will be created. * @param internalPath - * - path of the file or directory relative to the package root. + * - path of the file or directory relative to the package root. * @param mediaTypeString - * - media type of stream. If unknown null can be used. + * - media type of stream. If unknown null can be used. */ public void insert(byte[] fileBytes, String internalPath, String mediaTypeString) { internalPath = normalizeFilePath(internalPath); @@ -1431,14 +1603,14 @@ public class OdfPackage implements Close try { setMediaTypeString(new String(fileBytes, "UTF-8")); } catch (UnsupportedEncodingException useEx) { - Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, - "ODF file could not be created as string!", useEx); + Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, "ODF file could not be created as string!", useEx); } return; } if (fileBytes != null) { mMemoryFileCache.put(internalPath, fileBytes); - // as DOM would overwrite data cache, any existing DOM cache will be deleted + // as DOM would overwrite data cache, any existing DOM cache will be + // deleted if (mPkgDoms.containsKey(internalPath)) { mPkgDoms.remove(internalPath); } @@ -1446,113 +1618,19 @@ public class OdfPackage implements Close updateFileEntry(ensureFileEntryExistence(internalPath), mediaTypeString); } - // changed to package access as the manifest interiors are an implementation detail + // changed to package access as the manifest interiors are an implementation + // detail Map getManifestEntries() { return mManifestEntries; } /** - * Get Manifest as String NOTE: This functionality should better be moved to - * a DOM based Manifest class - * - * @return the /META-INF/manifest.xml as a String - */ - public String getManifestAsString() { - if (mManifestEntries == null) { - return null; - } else { - StringBuilder buf = new StringBuilder(); - buf.append("\n"); - buf.append("\n"); - Iterator it = new TreeSet(mManifestEntries.keySet()).iterator(); - while (it.hasNext()) { - String key = it.next(); - String s = null; - OdfFileEntry fileEntry = mManifestEntries.get(key); - if (fileEntry != null) { - s = fileEntry.getPath(); - // only directories with a mimetype (documents) will be written into the manifest.xml - if (s != null && !s.endsWith(SLASH) || !fileEntry.getMediaTypeString().equals(EMPTY_STRING)) { - buf.append(" 0) { - buf.append(" manifest:size=\""); - buf.append(i); - buf.append("\""); - } - EncryptionData enc = fileEntry.getEncryptionData(); - if (enc != null) { - buf.append(">\n"); - buf.append(" \n"); - Algorithm alg = enc.getAlgorithm(); - if (alg != null) { - buf.append(" \n"); - } - KeyDerivation keyDerivation = enc.getKeyDerivation(); - if (keyDerivation != null) { - buf.append(" \n"); - } - buf.append(" \n"); - buf.append(" \n"); - } else { - buf.append("/>\n"); - } - } - } - } - buf.append(""); - return buf.toString(); - } - } - - /** * Get package (sub-) content as byte array - * - * @param internalPath relative documentURL to the package content + * + * @param internalPath + * relative documentURL to the package content * @return the unzipped package content as byte array + * @throws java.lang.Exception */ public byte[] getBytes(String internalPath) { // if path is null or empty return null @@ -1573,29 +1651,11 @@ public class OdfPackage implements Close return null; } } - // if the file is "/META-INF/manifest.xml" - } else if (internalPath.equals(OdfPackage.OdfFile.MANIFEST.internalPath)) { - if (mManifestEntries == null) { - // manifest was not present - return null; - } - String s = getManifestAsString(); - if (s == null) { - return null; - } else { - try { - data = s.getBytes("UTF-8"); - } catch (UnsupportedEncodingException ex) { - Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex); - } - } - // if the path is already loaded as DOM (highest priority) } else if (mPkgDoms.get(internalPath) != null) { data = flushDom(mPkgDoms.get(internalPath)); mMemoryFileCache.put(internalPath, data); // if the path's file was cached to memory (second high priority) - } else if (mManifestEntries.containsKey(internalPath) - && mMemoryFileCache.get(internalPath) != null) { + } else if (mManifestEntries.containsKey(internalPath) && mMemoryFileCache.get(internalPath) != null) { data = mMemoryFileCache.get(internalPath); // if the path's file was cached to disc (lowest priority) @@ -1611,12 +1671,19 @@ public class OdfPackage implements Close ByteArrayOutputStream out = new ByteArrayOutputStream(); StreamHelper.transformStream(inputStream, out); data = out.toByteArray(); - // store for further usage; do not care about manifest: that - // is handled exclusively + // decrypt data as needed + if (!(internalPath.equals(OdfFile.MEDIA_TYPE.getPath()) || internalPath.equals(OdfFile.MANIFEST.getPath()))) { + OdfFileEntry manifestEntry = getManifestEntries().get(internalPath); + EncryptionDataElement encryptionDataElement = manifestEntry.getEncryptionData(); + if (encryptionDataElement != null) { + data = decryptData(data, manifestEntry, encryptionDataElement); + } + } + // store for further usage; do not care about manifest: + // that is handled exclusively mMemoryFileCache.put(internalPath, data); } } catch (IOException ex) { - //Catching IOException here should be fine: in-memory operations only Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex); } finally { try { @@ -1632,10 +1699,232 @@ public class OdfPackage implements Close return data; } + // encrypt data and update manifest. + private byte[] encryptData(byte[] data, OdfFileEntry fileEntry) { + byte[] encryptedData = null; + try { + // 1.The original uncompressed, unencrypted size is + // contained in the manifest:size. + fileEntry.setSize(data.length); + + // 2.Compress with the "deflate" algorithm + Deflater compresser = new Deflater(Deflater.DEFLATED, true); + compresser.setInput(data); + compresser.finish(); + byte[] compressedData = new byte[data.length]; + int compressedDataLength = compresser.deflate(compressedData); + + // 3. The start key is generated: the byte sequence + // representing the password in UTF-8 is used to + // generate a 20-byte SHA1 digest. + byte[] passBytes = newPwd.getBytes("UTF-8"); + MessageDigest md = MessageDigest.getInstance("SHA1"); + passBytes = md.digest(passBytes); + // 4. Checksum specifies a digest in BASE64 encoding + // that can be used to detect password correctness. The + // digest is build from the compressed unencrypted file. + md.reset(); + md.update(compressedData, 0, (compressedDataLength > 1024 ? 1024 : compressedDataLength)); + byte[] checksumBytes = new byte[20]; + md.digest(checksumBytes, 0, 20); + + // 5. For each file, a 16-byte salt is generated by a random + // generator. + // The salt is a BASE64 encoded binary sequence. + SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); + byte[] salt = new byte[16]; + secureRandom.nextBytes(salt); + + // char passChars[] = new String(passBytes, "UTF-8").toCharArray(); + /* + * char passChars[] = new char[20]; for (int i = 0; i < + * passBytes.length; i++) { passChars[i] = (char) + * ((passBytes[i]+256)%256); + * //System.out.println("passChars[i]:"+passChars + * [i]+", passBytes[i]"+passBytes[i]); } + * + * //char passChars[] = getChars(passBytes); // 6. The PBKDF2 + * algorithm based on the HMAC-SHA-1 function is used for the key + * derivation. SecretKeyFactory factory = + * SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); // 7. The + * salt is used together with the start key to derive a unique + * 128-bit key for each file. // The default iteration count for the + * algorithm is 1024. KeySpec spec = new PBEKeySpec(passChars, salt, + * 1024, 128); SecretKey skey = factory.generateSecret(spec); byte[] + * raw = skey.getEncoded(); // algorithm-name="Blowfish CFB" + * SecretKeySpec skeySpec = new SecretKeySpec(raw, "Blowfish"); + */ + + byte[] dk = derivePBKDF2Key(passBytes, salt, 1024, 16); + SecretKeySpec key = new SecretKeySpec(dk, "Blowfish"); + // 8.The files are encrypted: The random number + // generator is used to generate the 8-byte initialization vector + // for the + // algorithm. The derived key is used together with the + // initialization + // vector to encrypt the file using the Blowfish algorithm in cipher + // feedback + // CFB mode. + Cipher cipher = Cipher.getInstance("Blowfish/CFB/NoPadding"); + // initialisation-vector specifies the byte-sequence used + // as an initialization vector to a encryption algorithm. The + // initialization vector is a BASE64 encoded binary sequence. + byte[] iv = new byte[8]; + secureRandom.nextBytes(iv); + IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); + cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec); + encryptedData = cipher.doFinal(compressedData, 0, compressedDataLength); + + // 9.update file entry encryption data. + String checksum = new Base64Binary(checksumBytes).toString(); + FileEntryElement fileEntryElement = fileEntry.getOdfElement(); + EncryptionDataElement encryptionDataElement = OdfElement.findFirstChildNode(EncryptionDataElement.class, fileEntryElement); + if (encryptionDataElement != null) { + fileEntryElement.removeChild(encryptionDataElement); + } + encryptionDataElement = fileEntryElement.newEncryptionDataElement(checksum, "SHA1/1K"); + String initialisationVector = new Base64Binary(iv).toString(); + AlgorithmElement algorithmElement = OdfElement.findFirstChildNode(AlgorithmElement.class, encryptionDataElement); + if (algorithmElement != null) { + encryptionDataElement.removeChild(algorithmElement); + } + algorithmElement = encryptionDataElement.newAlgorithmElement("Blowfish CFB", initialisationVector); + String saltStr = new Base64Binary(salt).toString(); + KeyDerivationElement keyDerivationElement = OdfElement.findFirstChildNode(KeyDerivationElement.class, encryptionDataElement); + if (keyDerivationElement != null) { + encryptionDataElement.removeChild(keyDerivationElement); + } + keyDerivationElement = encryptionDataElement.newKeyDerivationElement(1024, "PBKDF2", saltStr); + StartKeyGenerationElement startKeyGenerationElement = OdfElement.findFirstChildNode(StartKeyGenerationElement.class, encryptionDataElement); + if (startKeyGenerationElement != null) { + encryptionDataElement.removeChild(startKeyGenerationElement); + } + encryptionDataElement.newStartKeyGenerationElement("SHA1").setKeySizeAttribute(20); + + // System.out.println("full-path=\""+ path +"\""); + // System.out.println("size=\""+ data.length +"\""); + // System.out.println("checksum=\""+ checksum +"\""); + // System.out.println("compressedData ="+compressedDataLength); + + } catch (Exception e) { + // throws NoSuchAlgorithmException, + // InvalidKeySpecException, NoSuchPaddingException, + // InvalidKeyException, + // InvalidAlgorithmParameterException, + // IllegalBlockSizeException, BadPaddingException + Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, e); + } + return encryptedData; + } + + private byte[] decryptData(byte[] data, OdfFileEntry manifestEntry, EncryptionDataElement encryptionDataElement) { + byte[] decompressData = null; + try { + KeyDerivationElement keyDerivationElement = OdfElement.findFirstChildNode(KeyDerivationElement.class, encryptionDataElement); + AlgorithmElement algorithmElement = OdfElement.findFirstChildNode(AlgorithmElement.class, encryptionDataElement); + String saltStr = keyDerivationElement.getSaltAttribute(); + String ivStr = algorithmElement.getInitialisationVectorAttribute(); + String checksum = encryptionDataElement.getChecksumAttribute(); + byte[] salt = Base64Binary.valueOf(saltStr).getBytes(); + byte[] iv = Base64Binary.valueOf(ivStr).getBytes(); + byte[] passBytes = oldPwd.getBytes("UTF-8"); + MessageDigest md = MessageDigest.getInstance("SHA-1"); + passBytes = md.digest(passBytes); + /* + * char passChars[] = new char[passBytes.length]; for(int i = 0; + * i 1024 ? 1024 : decryptedData.length)); + byte[] checksumBytes = new byte[20]; + md.digest(checksumBytes, 0, 20); + String newChecksum = new Base64Binary(checksumBytes).toString(); + if (newChecksum.equals(checksum)) { + // decompress the bytes + Inflater decompresser = new Inflater(true); + decompresser.setInput(decryptedData); + decompressData = new byte[manifestEntry.getSize()]; + decompresser.inflate(decompressData); + decompresser.end(); + } else { + throw new OdfDecryptedException("The given password is wrong, please check it."); + } + } catch (Exception e) { + Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, e); + } + return decompressData; + } + + // derive PBKDF2Key (reference http://www.ietf.org/rfc/rfc2898.txt) + byte[] derivePBKDF2Key(byte[] password, byte[] salt, int iterationCount, int keyLength) throws NoSuchAlgorithmException, InvalidKeyException { + SecretKeySpec keyspec = new SecretKeySpec(password, "HmacSHA1"); + Mac hmac = Mac.getInstance("HmacSHA1"); + hmac.init(keyspec); + // length in octets of HmacSHA1 function output, a positive integer + int hmacLen = hmac.getMacLength(); + // let l be the number of hLen-octet blocks in the derived key, rounding + // up, + // l = CEIL (dkLen / hLen) Here, CEIL (x) is the smallest integer + // greater than, or equal to, x. + int l = (keyLength % hmacLen > 0) ? (keyLength / hmacLen + 1) : (keyLength / hmacLen); + // let r be the number of octets in the last block: r = dkLen - (l - 1) + // * hLen . + int r = keyLength - (l - 1) * hmacLen; + byte T[] = new byte[l * hmacLen]; + int offset = 0; + // For each block of the derived key apply the function F defined below + // to the password P, the salt S, the iteration count c, and + // the block index to compute the block: + for (int i = 1; i <= l; i++) { + byte Ur[] = new byte[hmacLen]; + byte Ui[] = new byte[salt.length + 4]; + System.arraycopy(salt, 0, Ui, 0, salt.length); + // Here, INT (i) is a four-octet encoding of the integer i, most + // significant octet first. + Ui[salt.length + 0] = (byte) (i >>> 24); + Ui[salt.length + 1] = (byte) (i >>> 16); + Ui[salt.length + 2] = (byte) (i >>> 8); + Ui[salt.length + 3] = (byte) (i); + // U_1 \xor U_2 \xor ... \xor U_c + for (int j = 0; j < iterationCount; j++) { + Ui = hmac.doFinal(Ui); + // XOR + for (int k = 0; k < T.length; k++) { + Ur[k] ^= Ui[k]; + } + } + System.arraycopy(Ur, 0, T, offset, hmacLen); + offset += hmacLen; + } + if (r < hmacLen) { + byte DK[] = new byte[keyLength]; + System.arraycopy(T, 0, DK, 0, keyLength); + return DK; + } + return T; + } + // Serializes a DOM tree into a byte array. - // Providing the counterpart of the generic Namespace handling of OdfFileDom. + // Providing the counterpart of the generic Namespace handling of + // OdfFileDom. private byte[] flushDom(Document dom) { - // if it is one of our DOM files we may flush all collected namespaces to the root element + // if it is one of our DOM files we may flush all collected namespaces + // to the root element if (dom instanceof OdfFileDom) { OdfFileDom odfDom = (OdfFileDom) dom; Map nsByUri = odfDom.getMapNamespacePrefixByUri(); @@ -1657,9 +1946,10 @@ public class OdfPackage implements Close } /** - * Get the latest version of package content as InputStream, as it would be saved. - * This might not be the original version once loaded from the package. - * + * Get the latest version of package content as InputStream, as it would be + * saved. This might not be the original version once loaded from the + * package. + * * @param internalPath * of the desired stream. * @return Inputstream of the ODF file within the package for the given @@ -1674,7 +1964,7 @@ public class OdfPackage implements Close // ZipFile class. [... 423 lines stripped ...]