blob: 33cba39fb6606422277dca02ade3c0dd0f2ffdb3 [file] [log] [blame]
/************************************************************************
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
* Copyright 2009 IBM. 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.
*
************************************************************************/
/* This file is derived from ODFDOM 0.8.6, and
* has been modified for Apache Taverna.
* (c) 2010-2014 University of Manchester
* (c) 2015 The Apache Software Foundation
*/
package org.apache.taverna.scufl2.ucfpackage.impl.odfdom.pkg;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import static java.util.zip.ZipEntry.DEFLATED;
import static java.util.zip.ZipEntry.STORED;
import static org.apache.taverna.scufl2.ucfpackage.impl.odfdom.pkg.StreamHelper.PAGE_SIZE;
import static org.apache.taverna.scufl2.ucfpackage.impl.odfdom.pkg.TempDir.newTempOdfDirectory;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
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.Source;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamSource;
import org.apache.taverna.scufl2.ucfpackage.impl.odfdom.pkg.manifest.Algorithm;
import org.apache.taverna.scufl2.ucfpackage.impl.odfdom.pkg.manifest.EncryptionData;
import org.apache.taverna.scufl2.ucfpackage.impl.odfdom.pkg.manifest.KeyDerivation;
import org.apache.taverna.scufl2.ucfpackage.impl.odfdom.pkg.manifest.OdfFileEntry;
import org.w3c.dom.Document;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* OdfPackage represents the package view to an OpenDocument document. The
* OdfPackage will be created from an ODF document and represents a copy of the
* loaded document, where files can be inserted and deleted. The changes take
* effect, when the OdfPackage is being made persistend by save().
*/
public class OdfPackage implements AutoCloseable {
/**
* This class solely exists to clean up after a package object has been
* removed by garbage collector. Finalizable classes are said to have slow
* garbage collection, so we don't make the whole OdfPackage finalizable.
*/
static private class OdfFinalizablePackage {
File mTempDirForDeletion;
OdfFinalizablePackage(File tempDir) {
mTempDirForDeletion = tempDir;
}
@Override
protected void finalize() {
if (mTempDirForDeletion != null)
TempDir.deleteTempOdfDirectory(mTempDirForDeletion);
}
}
private final Logger mLog = Logger.getLogger(OdfPackage.class.getName());
public enum OdfFile {
IMAGE_DIRECTORY("Pictures"), MANIFEST("META-INF/manifest.xml"), MEDIA_TYPE(
"mimetype");
private final String packagePath;
OdfFile(String packagePath) {
this.packagePath = packagePath;
}
public String getPath() {
return packagePath;
}
}
private static HashSet<String> 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" };
for (int i = 0; i < typelist.length; i++)
mCompressedFileTypes.add(typelist[i]);
}
// Static parts of file references
private static final String TWO_DOTS = "..";
private static final String SLASH = "/";
private static final String COLON = ":";
private static final String EMPTY_STRING = "";
private static final String XML_MEDIA_TYPE = "text/xml";
// temp Dir for this ODFpackage (2DO: temp dir handling will be removed most
// likely)
private File mTempDirParent;
private File mTempDir;
// only used indirectly for its finalizer (garbage collection)
@SuppressWarnings("unused")
private OdfFinalizablePackage mFinalize;
// some well known streams inside ODF packages
private String mMediaType;
private List<String> mPackageEntries;
private ZipHelper mZipFile;
private HashMap<String, ZipEntry> mZipEntries;
private HashMap<String, Document> mContentDoms;
private HashMap<String, byte[]> mContentStreams;
private HashMap<String, File> mTempFiles;
private boolean mUseTempFile;
private List<String> mManifestList;
private HashMap<String, OdfFileEntry> mManifestEntries;
private String mBaseURI;
private Resolver mResolver;
/**
* Creates the ODFPackage as an empty Package. For setting a specific temp
* directory, set the System variable org.odftoolkit.odfdom.tmpdir:<br>
* <code>System.setProperty("org.odftoolkit.odfdom.tmpdir");</code>
*/
private OdfPackage() {
mMediaType = null;
mResolver = null;
mTempDir = null;
mTempDirParent = null;
mPackageEntries = new LinkedList<String>();
mZipEntries = new HashMap<String, ZipEntry>();
mContentDoms = new HashMap<String, Document>();
mContentStreams = new HashMap<String, byte[]>();
mTempFiles = new HashMap<String, File>();
mManifestList = new LinkedList<String>();
// get a temp directory for everything
String userPropDir = System.getProperty("org.odftoolkit.odfdom.tmpdir");
if (userPropDir != null)
mTempDirParent = new File(userPropDir);
// specify whether temporary files are able to used.
String userPropTempEnable = System
.getProperty("org.odftoolkit.odfdom.tmpfile.disable");
mUseTempFile = (userPropTempEnable == null)
|| !userPropTempEnable.equalsIgnoreCase("true");
}
public static OdfPackage create() throws Exception {
OdfPackage odfPackage = new OdfPackage();
odfPackage.mManifestEntries = new HashMap<>();
// We just need some dummy XML first
odfPackage.insert("<x />".getBytes(), OdfFile.MANIFEST.getPath(),
XML_MEDIA_TYPE);
return odfPackage;
}
/**
* Creates an OdfPackage from the OpenDocument provided by a filePath.
*
* <p>
* OdfPackage relies on the file being available for read access over the
* whole lifecycle of OdfPackage.
* </p>
*
* @param odfPath
* - the path to the ODF document.
* @throws java.lang.Exception
* - if the package could not be created
*/
private OdfPackage(String odfPath) throws Exception {
this();
initialize(new File(odfPath));
}
/**
* Creates an OdfPackage from the OpenDocument provided by a File.
*
* <p>
* OdfPackage relies on the file being available for read access over the
* whole lifecycle of OdfPackage.
* </p>
*
* @param odfFile
* - a file representing the ODF document
* @throws java.lang.Exception
* - if the package could not be created
*/
private OdfPackage(File odfFile) throws Exception {
this();
initialize(odfFile);
}
/**
* Creates an OdfPackage from the OpenDocument provided by a InputStream.
*
* <p>
* 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.
* </p>
*
* @param odfStream
* - an inputStream representing the ODF package
* @throws java.lang.Exception
* - if the package could not be created
*/
private OdfPackage(InputStream odfStream) throws Exception {
this();
if (mUseTempFile) {
File tempFile = newTempSourceFile(odfStream);
initialize(tempFile);
} else {
initialize(odfStream);
}
}
/**
* Loads an OdfPackage from the given filePath.
*
* <p>
* OdfPackage relies on the file being available for read access over the
* whole lifecycle of OdfPackage.
* </p>
*
* @param odfPath
* - the filePath to 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(String odfPath) throws Exception {
return new OdfPackage(odfPath);
}
/**
* Loads an OdfPackage from the OpenDocument provided by a File.
*
* <p>
* OdfPackage relies on the file being available for read access over the
* whole lifecycle of OdfPackage.
* </p>
*
* @param odfFile
* - a File to loadPackage content from
* @return the OpenDocument document represented as an OdfPackage
* @throws java.lang.Exception
* - if the package could not be loaded
*/
public static OdfPackage loadPackage(File odfFile) throws Exception {
return new OdfPackage(odfFile);
}
/**
* Creates an OdfPackage from the OpenDocument provided by a InputStream.
*
* <p>
* 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.
* </p>
*
* @param odfStream
* - 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 odfStream)
throws Exception {
return new OdfPackage(odfStream);
}
// Initialize using memory instead temporary disc
private void initialize(InputStream odfStream) throws Exception {
ByteArrayOutputStream tempBuf = new ByteArrayOutputStream();
StreamHelper.stream(odfStream, tempBuf);
byte[] mTempByteBuf = tempBuf.toByteArray();
tempBuf.close();
if (mTempByteBuf.length < 3)
throw new IllegalArgumentException(
"An empty file was tried to be opened as ODF package!");
mZipFile = new ZipHelper(mTempByteBuf);
Enumeration<? extends ZipEntry> entries = mZipFile.entries();
if (!entries.hasMoreElements())
throw new IllegalArgumentException(
"It was not possible to unzip the file!");
do {
ZipEntry zipEntry = entries.nextElement();
mZipEntries.put(zipEntry.getName(), zipEntry);
/*
* TODO: think about if the additional list mPackageEntries is
* necessary - shouldn't everything be part of one of the other
* lists? maybe keep this as "master", rename it?
*/
mPackageEntries.add(zipEntry.getName());
if (zipEntry.getName().equals(
OdfPackage.OdfFile.MEDIA_TYPE.getPath())) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamHelper.stream(mZipFile.getInputStream(zipEntry), out);
try {
mMediaType = new String(out.toByteArray(), 0,
out.size(), "UTF-8");
} catch (UnsupportedEncodingException ex) {
mLog.log(SEVERE, null, ex);
}
}
} while (entries.hasMoreElements());
}
// Initialize using temporary directory on hard disc
private void initialize(File odfFile) throws Exception {
mBaseURI = getBaseURIFromFile(odfFile);
if (mTempDirParent == null) {
/*
* getParentFile() returns already java.io.tmpdir when package is an
* odfStream
*/
mTempDirParent = odfFile.getAbsoluteFile().getParentFile();
if (!mTempDirParent.canWrite())
mTempDirParent = null; // java.io.tmpdir will be used implicitly
}
try {
mZipFile = new ZipHelper(new ZipFile(odfFile));
} catch (Exception e) {
if (odfFile.length() < 3)
throw new IllegalArgumentException("The empty file '"
+ odfFile.getPath() + "' is no ODF package!", e);
throw new IllegalArgumentException("Could not unzip the file "
+ odfFile.getPath(), e);
}
Enumeration<? extends ZipEntry> entries = mZipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
mZipEntries.put(zipEntry.getName(), zipEntry);
/*
* TODO: think about if the additional list mPackageEntries is
* necessary - shouldn't everything be part of one of the other
* lists? maybe keep this as "master", rename it?
*/
mPackageEntries.add(zipEntry.getName());
if (zipEntry.getName().equals(
OdfPackage.OdfFile.MEDIA_TYPE.getPath())) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamHelper.stream(mZipFile.getInputStream(zipEntry), out);
try {
mMediaType = new String(out.toByteArray(), 0, out.size(),
"UTF-8");
} catch (UnsupportedEncodingException ex) {
mLog.log(Level.SEVERE, null, ex);
}
}
}
}
private File newTempSourceFile(InputStream odfStream) throws Exception {
// no idea yet what type of file this will be, so we take .tmp
File odfFile = new File(getTempDir(), "theFile.tmp");
// copy stream to temp file
FileOutputStream os = new FileOutputStream(odfFile);
StreamHelper.stream(odfStream, os);
os.close();
return odfFile;
}
/**
* Set the baseURI for this ODF package. NOTE: Should only be set during
* saving the package.
*/
void setBaseURI(String baseURI) {
mBaseURI = baseURI;
}
/**
* Get the URI, where this ODF package is stored.
*
* @return the URI to the ODF package. Returns null if package is not stored
* yet.
*/
public String getBaseURI() {
return mBaseURI;
}
/**
* 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 getMediaType() {
return mMediaType;
}
/**
* Set the media type of the ODF package (equal to media type of ODF root
* document)
*
* @param mediaType
* string of this ODF package
*/
public void setMediaType(String mediaType) {
mMediaType = mediaType;
mPackageEntries.remove(OdfPackage.OdfFile.MEDIA_TYPE.getPath());
if (mMediaType != null)
mPackageEntries.add(0, OdfPackage.OdfFile.MEDIA_TYPE.getPath());
}
/**
*
* Get an OdfFileEntry for the packagePath NOTE: This method should be
* better moved to a DOM inherited Manifest class
*
* @param packagePath
* The relative package path within the ODF package
* @return The manifest file entry will be returned.
*/
public OdfFileEntry getFileEntry(String packagePath) {
packagePath = ensureValidPackagePath(packagePath);
return getManifestEntries().get(packagePath);
}
/**
* Get a OdfFileEntries from the manifest file (i.e. /META/manifest.xml")
*
* @return The manifest file entries will be returned.
*/
public Set<String> getFileEntries() {
return getManifestEntries().keySet();
}
/**
*
* Check existence of a file in the package.
*
* @param packagePath
* The relative package filePath within the ODF package
* @return True if there is an entry and a file for the given filePath
*/
public boolean contains(String packagePath) {
packagePath = ensureValidPackagePath(packagePath);
return mPackageEntries.contains(packagePath);
/* TODO: return true for later added stuff */
// return (mPackageEntries.contains(packagePath) &&
// (mTempFiles.get(packagePath) != null ||
// mContentStreams.get(packagePath)!=null) && getFileEntry(packagePath)
// != null);
}
/**
* Save the package to given filePath.
*
* @param odfPath
* - the path to the ODF package destination
* @throws java.lang.Exception
* - if the package could not be saved
*/
public void save(String odfPath) throws Exception {
File f = new File(odfPath);
save(f);
}
/**
* 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 odfFile
* - the File to save the ODF package to
* @throws java.lang.Exception
* - if the package could not be saved
*/
public void save(File odfFile) throws Exception {
String baseURI = odfFile.getCanonicalFile().toURI().toString();
if (File.separatorChar == '\\')
baseURI = baseURI.replaceAll("\\\\", SLASH);
if (baseURI.equals(mBaseURI))
/* save to the same file: cache everything first */
// TODO: 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(odfFile);
save(fos, baseURI);
}
public void save(OutputStream odfStream) throws Exception {
save(odfStream, null);
}
/**
* Save an ODF document to the OutputStream.
*
* @param odfStream
* - the OutputStream to insert content to
* @param baseURI
* - a URI for the package to be stored
* @throws java.lang.Exception
* - if the package could not be saved
*/
private void save(OutputStream odfStream, String baseURI) throws Exception {
mBaseURI = baseURI;
OdfFileEntry rootEntry = getManifestEntries().get(SLASH);
if (rootEntry == null) {
rootEntry = new OdfFileEntry(SLASH, mMediaType);
mManifestList.add(0, rootEntry.getPath());
} else
rootEntry.setMediaType(mMediaType);
ZipOutputStream zos = new ZipOutputStream(odfStream);
long modTime = (new java.util.Date()).getTime();
/*
* move manifest to first place to ensure it is written first into the
* package zip file
*/
if (mPackageEntries.contains(OdfFile.MEDIA_TYPE.getPath())) {
mPackageEntries.remove(OdfFile.MEDIA_TYPE.getPath());
mPackageEntries.add(0, OdfFile.MEDIA_TYPE.getPath());
}
Iterator<String> it = mPackageEntries.iterator();
while (it.hasNext()) {
String key = it.next();
byte[] data = getBytes(key);
ZipEntry ze = mZipEntries.get(key);
if (ze == null)
ze = new ZipEntry(key);
ze.setTime(modTime);
ze.setMethod(isFileCompressed(key) ? DEFLATED : STORED);
CRC32 crc = new CRC32();
if (data != null) {
crc.update(data);
ze.setSize(data.length);
} else {
ze.setMethod(STORED);
ze.setSize(0);
}
ze.setCrc(crc.getValue());
ze.setCompressedSize(-1);
zos.putNextEntry(ze);
if (data != null)
zos.write(data, 0, data.length);
zos.closeEntry();
mZipEntries.put(key, ze);
}
zos.close();
odfStream.flush();
}
/**
* if the file is to be compressed,return true
*
* @param key
* --file name
* @return false if the file is not to be compressed
*/
private boolean isFileCompressed(String key) {
if (key.equals(OdfPackage.OdfFile.MEDIA_TYPE.getPath()))
return false;
boolean result = true;
if (key.lastIndexOf(".") > 0) {
String endWith = key.substring(key.lastIndexOf(".") + 1,
key.length());
if (mCompressedFileTypes.contains(endWith.toLowerCase()))
result = false;
}
return result;
}
/**
* If this file is saved to itself, we have to cache it. It is not possible
* to read and write from the same zip file at the same time, so the content
* must be read and stored in memory.
*/
private void cacheContent() throws Exception {
// read all entries
getManifestEntries();
Iterator<String> entries = mZipEntries.keySet().iterator();
while (entries.hasNext()) {
// open all entries once so the data is cached
ZipEntry nextElement = mZipEntries.get(entries.next());
String entryPath = nextElement.getName();
getBytes(entryPath);
}
}
/**
* Close the OdfPackage after it is no longer needed. Even after saving it
* is still necessary to close the package to have again full access about
* the file. Closing the OdfPackage will release all temporary created data.
* Do this as the last action to free resources. Closing an already closed
* document has no effect.
*/
@Override
public void close() {
if (mTempDir != null)
TempDir.deleteTempOdfDirectory(mTempDir);
try {
if (mZipFile != null)
mZipFile.close();
} catch (IOException ex) {
// log exception and continue
Logger.getLogger(OdfPackage.class.getName()).log(INFO, null, ex);
}
// release all stuff - this class is impossible to use afterwards
mZipFile = null;
mTempDirParent = null;
mTempDir = null;
mMediaType = null;
mPackageEntries = null;
mZipEntries = null;
mContentDoms = null;
mContentStreams = null;
mTempFiles = null;
mManifestList = null;
mManifestEntries = null;
mBaseURI = null;
mResolver = null;
}
/**
* Data was updated, update mZipEntry and OdfFileEntry as well
*/
private void entryUpdate(String packagePath) throws Exception,
SAXException, TransformerConfigurationException,
TransformerException, ParserConfigurationException {
byte[] data = getBytes(packagePath);
int size = (data == null ? 0 : data.length);
OdfFileEntry fileEntry = getManifestEntries().get(packagePath);
ZipEntry zipEntry = mZipEntries.get(packagePath);
if (fileEntry != null) {
// if (XML_MEDIA_TYPE.equals(fileEntry.getMediaType())) {
// fileEntry.setSize(-1);
// } else {
fileEntry.setSize(size);
// }
}
if (zipEntry == null)
return;
zipEntry.setSize(size);
CRC32 crc = new CRC32();
if ((data != null) && size > 0)
crc.update(data);
zipEntry.setCrc(crc.getValue());
zipEntry.setCompressedSize(-1);
long modTime = (new java.util.Date()).getTime();
zipEntry.setTime(modTime);
}
/**
* Parse the Manifest file
*/
void parseManifest() throws Exception {
InputStream is = getInputStream(OdfPackage.OdfFile.MANIFEST.packagePath);
if (is == null) {
mManifestList = null;
mManifestEntries = null;
return;
}
mManifestList = new LinkedList<>();
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(false);
try {
factory.setFeature(
"http://apache.org/xml/features/nonvalidating/load-external-dtd",
false);
} catch (Exception ex) {
mLog.log(Level.SEVERE, null, ex);
}
SAXParser parser = factory.newSAXParser();
XMLReader xmlReader = parser.getXMLReader();
// 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
xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes",
true);
try {
// More details at http://xerces.apache.org/xerces2-j/features.html#xmlns-uris
xmlReader.setFeature("http://xml.org/sax/features/xmlns-uris", true);
} catch (SAXException ex) {
mLog.log(Level.FINE, "Can't set XML reader feature xmlns-uris", ex);
}
String uri = mBaseURI + OdfPackage.OdfFile.MANIFEST.packagePath;
xmlReader.setEntityResolver(getEntityResolver());
xmlReader.setContentHandler(new ManifestContentHandler());
InputSource ins = new InputSource(is);
ins.setSystemId(uri);
xmlReader.parse(ins);
mContentStreams.remove(OdfPackage.OdfFile.MANIFEST.packagePath);
entryUpdate(OdfPackage.OdfFile.MANIFEST.packagePath);
}
/**
* Checks if packagePath is not null nor empty and not an external reference
*/
private String ensureValidPackagePath(String packagePath) {
if (packagePath == null) {
String errMsg = "The packagePath given by parameter is NULL!";
mLog.severe(errMsg);
throw new IllegalArgumentException(errMsg);
}
if (packagePath.equals(EMPTY_STRING)) {
String errMsg = "The packagePath given by parameter is an empty string!";
mLog.severe(errMsg);
throw new IllegalArgumentException(errMsg);
}
if (packagePath.indexOf('\\') != -1)
packagePath = packagePath.replace('\\', '/');
if (isExternalReference(packagePath)) {
String errMsg = "The packagePath given by parameter '"
+ packagePath
+ "' is not an internal ODF package path!";
mLog.severe(errMsg);
throw new IllegalArgumentException(errMsg);
}
return packagePath;
}
/**
* add a directory to the OdfPackage
*/
private void addDirectory(String packagePath) throws Exception {
packagePath = ensureValidPackagePath(packagePath);
if ((packagePath.length() < 1)
|| (packagePath.charAt(packagePath.length() - 1) != '/'))
packagePath = packagePath + SLASH;
insert((byte[]) null, packagePath, null);
}
/**
* Insert DOM tree into OdfPackage. An existing file will be replaced.
*
* @param fileDOM
* - XML DOM tree to be inserted as file
* @param packagePath
* - relative filePath 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
* when the DOM tree could not be inserted
*/
public void insert(Document fileDOM, String packagePath, String mediaType)
throws Exception {
packagePath = ensureValidPackagePath(packagePath);
try {
if (mManifestEntries == null)
parseManifest();
} catch (Exception e) {
throw new RuntimeException(e);
}
if (mediaType == null)
mediaType = XML_MEDIA_TYPE;
String d = EMPTY_STRING;
StringTokenizer tok = new StringTokenizer(packagePath, SLASH);
while (tok.hasMoreTokens()) {
String s = tok.nextToken();
if (EMPTY_STRING.equals(d))
d = s + SLASH;
else
d = d + s + SLASH;
if (tok.hasMoreTokens() && !mPackageEntries.contains(d))
addDirectory(d);
}
mContentStreams.remove(packagePath);
if (fileDOM == null)
mContentDoms.remove(packagePath);
else
mContentDoms.put(packagePath, fileDOM);
if (!mPackageEntries.contains(packagePath))
mPackageEntries.add(packagePath);
try {
if (!OdfPackage.OdfFile.MANIFEST.packagePath.equals(packagePath)) {
if (mManifestEntries != null
&& mManifestEntries.get(packagePath) == null) {
OdfFileEntry fileEntry = new OdfFileEntry(packagePath,
mediaType);
mManifestEntries.put(packagePath, fileEntry);
mManifestList.add(packagePath);
}
} else
parseManifest();
// try to get the package from our cache
ZipEntry ze = mZipEntries.get(packagePath);
if (ze == null) {
ze = new ZipEntry(packagePath);
ze.setMethod(DEFLATED);
mZipEntries.put(packagePath, ze);
}
if (!isFileCompressed(packagePath))
ze.setMethod(STORED);
entryUpdate(packagePath);
} catch (SAXException se) {
throw new Exception("SAXException:" + se.getMessage());
} catch (ParserConfigurationException pce) {
throw new Exception("ParserConfigurationException:"
+ pce.getMessage());
} catch (TransformerConfigurationException tce) {
throw new Exception("TransformerConfigurationException:"
+ tce.getMessage());
} catch (TransformerException te) {
throw new Exception("TransformerException:" + te.getMessage());
}
}
/**
* returns true if a DOM tree has been requested for given sub-content of
* OdfPackage
*
* @param packagePath
* - a path inside the OdfPackage eg to a content.xml stream
* @return - wether the package class internally has a DOM representation
* for the given path
*/
public boolean hasDom(String packagePath) {
return (mContentDoms.get(packagePath) != null);
}
/**
* Gets org.w3c.dom.Document for XML file contained in package.
*
* @param packagePath
* - a path inside the OdfPackage eg to a content.xml stream
* @return an org.w3c.dom.Document
* @throws SAXException
* @throws ParserConfigurationException
* @throws Exception
* @throws IllegalArgumentException
* @throws TransformerConfigurationException
* @throws TransformerException
*/
public Document getDom(String packagePath) throws SAXException,
ParserConfigurationException, Exception, IllegalArgumentException,
TransformerConfigurationException, TransformerException {
Document doc = mContentDoms.get(packagePath);
if (doc != null)
return doc;
InputStream is = getInputStream(packagePath);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(false);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(getEntityResolver());
String uri = getBaseURI() + packagePath;
// if (mErrorHandler != null) {
// builder.setErrorHandler(mErrorHandler);
// }
InputSource ins = new InputSource(is);
ins.setSystemId(uri);
doc = builder.parse(ins);
if (doc != null) {
mContentDoms.put(packagePath, doc);
// mContentStreams.remove(packagePath);
}
return doc;
}
/**
* Inserts InputStream into an OdfPackage. An existing file will be
* replaced.
*
* @param sourceURI
* - the source URI to the file to be inserted into the package.
* @param mediaType
* - media type of stream. Set to null if unknown
* @param packagePath
* - relative filePath where the tree should be inserted as XML
* file
* @throws java.lang.Exception
* In case the file could not be saved
*/
public void insert(URI sourceURI, String packagePath, String mediaType)
throws Exception {
InputStream is = null;
if (sourceURI.isAbsolute())
// if the URI is absolute it can be converted to URL
is = sourceURI.toURL().openStream();
else {
// otherwise create a file class to open the stream
is = new FileInputStream(sourceURI.toString());
// TODO: error handling in this case! -> allow method insert(URI,
// ppath, mtype)?
}
insert(is, packagePath, mediaType);
}
/**
* 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 mediaType
* - media type of stream. Set to null if unknown
* @param packagePath
* - relative filePath where the tree should be inserted as XML
* file
* @throws java.lang.Exception
* In case the file could not be saved
*/
public void insert(InputStream fileStream, String packagePath,
String mediaType) throws Exception {
packagePath = ensureValidPackagePath(packagePath);
if (fileStream == null) {
insert((byte[]) null, packagePath, mediaType);
return;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedInputStream bis = new BufferedInputStream(fileStream);
StreamHelper.stream(bis, baos);
byte[] data = baos.toByteArray();
insert(data, packagePath, mediaType);
// image should not be stored in memory but on disc
if (!packagePath.endsWith(".xml")
&& !packagePath.equals(OdfPackage.OdfFile.MEDIA_TYPE.getPath())
&& mUseTempFile) {
// insertOutputStream to filesystem
File tempFile = new File(getTempDir(), packagePath);
File parent = tempFile.getParentFile();
parent.mkdirs();
try (OutputStream fos = new BufferedOutputStream(
new FileOutputStream(tempFile))) {
fos.write(data);
}
mTempFiles.put(packagePath, tempFile);
mContentStreams.remove(packagePath);
}
}
/**
* Insert byte array into OdfPackage. An existing file will be replaced.
*
* @param fileBytes
* - data of the file stream to be stored in package
* @param mediaType
* - media type of stream. Set to null if unknown
* @param packagePath
* - relative filePath where the DOM tree should be inserted as
* XML file
* @throws java.lang.Exception
* when the DOM tree could not be inserted
*/
public void insert(byte[] fileBytes, String packagePath, String mediaType)
throws Exception {
packagePath = ensureValidPackagePath(packagePath);
String d = EMPTY_STRING;
// 2DO: Test tokenizer for whitespaces..
StringTokenizer tok = new StringTokenizer(packagePath, SLASH);
while (tok.hasMoreTokens()) {
String s = tok.nextToken();
d = (EMPTY_STRING.equals(d) ? s + SLASH : d + s + SLASH);
if (tok.hasMoreTokens() && !mPackageEntries.contains(d)) {
addDirectory(d);
/*
* add manifest entry for folder if not already existing media
* type for folder has to be set for embedded objects
*/
if (!OdfPackage.OdfFile.MANIFEST.packagePath.equals(d)
&& mediaType != null
&& getManifestEntries().get(d) == null) {
OdfFileEntry fileEntry = new OdfFileEntry(d, mediaType, -1);
getManifestEntries().put(d, fileEntry);
if (!mManifestList.contains(d))
mManifestList.add(d);
}
}
}
try {
if (OdfPackage.OdfFile.MEDIA_TYPE.getPath().equals(packagePath)) {
try {
setMediaType(new String(fileBytes, "UTF-8"));
} catch (UnsupportedEncodingException useEx) {
mLog.log(WARNING,
"ODF file could not be created as string!", useEx);
}
return;
}
if (fileBytes == null)
mContentStreams.remove(packagePath);
else
mContentStreams.put(packagePath, fileBytes);
if (!mPackageEntries.contains(packagePath))
mPackageEntries.add(packagePath);
if (!OdfPackage.OdfFile.MANIFEST.packagePath.equals(packagePath)) {
if (mediaType != null
&& getManifestEntries().get(packagePath) == null) {
OdfFileEntry fileEntry = new OdfFileEntry(packagePath,
mediaType);
getManifestEntries().put(packagePath, fileEntry);
if (!mManifestList.contains(packagePath))
mManifestList.add(packagePath);
}
} else
parseManifest();
ZipEntry ze = mZipEntries.get(packagePath);
if (ze != null) {
ze = new ZipEntry(packagePath);
ze.setMethod(DEFLATED);
mZipEntries.put(packagePath, ze);
// 2DO Svante: No dependency to layer above!
if (isFileCompressed(packagePath) == false)
ze.setMethod(STORED);
}
entryUpdate(packagePath);
} catch (SAXException se) {
throw new Exception("SAXException:" + se.getMessage());
} catch (ParserConfigurationException pce) {
throw new Exception("ParserConfigurationException:"
+ pce.getMessage());
} catch (TransformerConfigurationException tce) {
throw new Exception("TransformerConfigurationException:"
+ tce.getMessage());
} catch (TransformerException te) {
throw new Exception("TransformerException:" + te.getMessage());
}
}
@SuppressWarnings("unused")
private void insert(ZipEntry zipe, byte[] content) {
if (content != null) {
if (zipe.getName().equals(OdfPackage.OdfFile.MEDIA_TYPE.getPath())) {
try {
mMediaType = new String(content, 0, content.length, "UTF-8");
} catch (UnsupportedEncodingException ex) {
mLog.log(Level.SEVERE, null, ex);
}
} else
mContentStreams.put(zipe.getName(), content);
}
if (!mPackageEntries.contains(zipe.getName()))
mPackageEntries.add(zipe.getName());
mZipEntries.put(zipe.getName(), zipe);
}
@SuppressWarnings("unused")
private void insert(ZipEntry zipe, File file) {
if (file != null)
mTempFiles.put(zipe.getName(), file);
if (!mPackageEntries.contains(zipe.getName()))
mPackageEntries.add(zipe.getName());
mZipEntries.put(zipe.getName(), zipe);
}
public HashMap<String, OdfFileEntry> getManifestEntries() {
if (mManifestEntries == null)
try {
parseManifest();
if (mManifestEntries == null)
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
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() {
StringBuilder buf = new StringBuilder();
buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
buf.append("<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">\n");
Iterator<String> it = mManifestList.iterator();
while (it.hasNext()) {
String key = it.next();
String s = null;
OdfFileEntry fileEntry = mManifestEntries.get(key);
if (fileEntry != null) {
buf.append(" <manifest:file-entry");
s = fileEntry.getMediaType();
if (s == null)
s = EMPTY_STRING;
buf.append(" manifest:media-type=\"");
buf.append(encodeXMLAttributes(s));
buf.append("\"");
s = fileEntry.getPath();
if (s == null)
s = EMPTY_STRING;
buf.append(" manifest:full-path=\"");
buf.append(encodeXMLAttributes(s));
buf.append("\"");
int i = fileEntry.getSize();
if (i > 0) {
buf.append(" manifest:size=\"");
buf.append(i);
buf.append("\"");
}
if (fileEntry.getVersion() != null) {
buf.append(" manifest:version=\"");
buf.append(encodeXMLAttributes(fileEntry.getVersion()));
buf.append("\"");
}
EncryptionData enc = fileEntry.getEncryptionData();
if (enc != null) {
buf.append(">\n");
buf.append(" <manifest:encryption-data>\n");
Algorithm alg = enc.getAlgorithm();
if (alg != null) {
buf.append(" <manifest:algorithm");
s = alg.getName();
if (s == null)
s = EMPTY_STRING;
buf.append(" manifest:algorithm-name=\"");
buf.append(encodeXMLAttributes(s));
buf.append("\"");
s = alg.getInitializationVector();
if (s == null)
s = EMPTY_STRING;
buf.append(" manifest:initialization-vector=\"");
buf.append(encodeXMLAttributes(s));
buf.append("\"/>\n");
}
KeyDerivation keyDerivation = enc.getKeyDerivation();
if (keyDerivation != null) {
buf.append(" <manifest:key-derivation");
s = keyDerivation.getName();
if (s == null)
s = EMPTY_STRING;
buf.append(" manifest:key-derivation-name=\"");
buf.append(encodeXMLAttributes(s));
buf.append("\"");
s = keyDerivation.getSalt();
if (s == null)
s = EMPTY_STRING;
buf.append(" manifest:salt=\"");
buf.append(encodeXMLAttributes(s));
buf.append("\"");
buf.append(" manifest:iteration-count=\"");
buf.append(keyDerivation.getIterationCount());
buf.append("\"/>\n");
}
buf.append(" </manifest:encryption-data>\n");
buf.append(" </manifest:file-entry>\n");
} else
buf.append("/>\n");
}
}
buf.append("</manifest:manifest>");
return buf.toString();
}
/**
* Get package (sub-) content as byte array
*
* @param packagePath
* relative filePath to the package content
* @return the unzipped package content as byte array
* @throws java.lang.Exception
*/
public byte[] getBytes(String packagePath) throws Exception {
packagePath = ensureValidPackagePath(packagePath);
byte[] data = null;
if (packagePath == null || packagePath.equals(EMPTY_STRING)) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
save(baos, mBaseURI);
return baos.toByteArray();
}
if (packagePath.equals(OdfPackage.OdfFile.MEDIA_TYPE.getPath())) {
if (mMediaType == null)
return null;
try {
data = mMediaType.getBytes("UTF-8");
} catch (UnsupportedEncodingException use) {
mLog.log(Level.SEVERE, null, use);
return null;
}
} else if (mPackageEntries.contains(packagePath)
&& mContentDoms.get(packagePath) != null) {
Document doc = mContentDoms.get(packagePath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// DOMXSImplementationSourceImpl dis = new
// org.apache.xerces.dom.DOMXSImplementationSourceImpl();
// DOMImplementationLS impl = (DOMImplementationLS)
// dis.getDOMImplementation("LS");
DOMImplementationLS impl = (DOMImplementationLS) DOMImplementationRegistry
.newInstance().getDOMImplementation("LS");
LSSerializer writer = impl.createLSSerializer();
LSOutput output = impl.createLSOutput();
output.setByteStream(baos);
writer.write(doc, output);
data = baos.toByteArray();
} else if (mPackageEntries.contains(packagePath)
&& mTempFiles.get(packagePath) != null) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try (InputStream is = new BufferedInputStream(new FileInputStream(
mTempFiles.get(packagePath)))) {
StreamHelper.stream(is, os);
}
os.close();
data = os.toByteArray();
} else if (mPackageEntries.contains(packagePath)
&& mContentStreams.get(packagePath) != null)
data = mContentStreams.get(packagePath);
else if (packagePath.equals(OdfPackage.OdfFile.MANIFEST.packagePath)) {
if (mManifestEntries == null)
// manifest was not present
return null;
String s = getManifestAsString();
if (s == null)
return null;
data = s.getBytes("UTF-8");
}
if (data == null) { // not yet stored data; retrieve it.
ZipEntry entry = mZipEntries.get(packagePath);
if (entry != null) {
InputStream inputStream = mZipFile.getInputStream(entry);
if (inputStream != null) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamHelper.stream(inputStream, out);
data = out.toByteArray();
// store for further usage; do not care about manifest: that
// is handled exclusively
mContentStreams.put(packagePath, data);
if (!mPackageEntries.contains(packagePath))
mPackageEntries.add(packagePath);
}
}
}
return data;
}
/**
* Get subcontent as InputStream
*
* @param packagePath
* of the desired stream.
* @return Inputstream of the ODF file within the package for the given
* path.
* @throws Exception
*/
public InputStream getInputStream(String packagePath) throws Exception {
packagePath = ensureValidPackagePath(packagePath);
if (packagePath.equals(OdfPackage.OdfFile.MANIFEST.packagePath)
&& (mManifestEntries == null)) {
ZipEntry entry = null;
if ((entry = mZipEntries.get(packagePath)) != null)
return mZipFile.getInputStream(entry);
}
if (mPackageEntries.contains(packagePath)
&& mTempFiles.get(packagePath) != null)
return new BufferedInputStream(new FileInputStream(
mTempFiles.get(packagePath)));
/*
* else we always cache here and return a ByteArrayInputStream because
* if we would return ZipFile getInputStream(entry) we would not be able
* to read 2 Entries at the same time. This is a limitation of the
* ZipFile class.
*
* As it would be quite a common thing to read the content.xml and the
* styles.xml simultaneously when using XSLT on OdfPackages we want to
* circumvent this limitation
*/
byte[] data = getBytes(packagePath);
if (data != null && data.length != 0)
return new ByteArrayInputStream(data);
return null;
}
/**
* Gets the InputStream containing whole OdfPackage.
*
* @return the ODF package as input stream
* @throws java.lang.Exception
* - if the package could not be read
*/
public InputStream getInputStream() throws Exception {
final PipedOutputStream os = new PipedOutputStream();
final PipedInputStream is = new PipedInputStream();
is.connect(os);
Thread thread1 = new Thread() {
@Override
public void run() {
try {
save(os, mBaseURI);
} catch (Exception e) {
}
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
try {
BufferedInputStream bis = new BufferedInputStream(is,
PAGE_SIZE);
BufferedOutputStream bos = new BufferedOutputStream(os,
PAGE_SIZE);
StreamHelper.stream(bis, bos);
is.close();
os.close();
} catch (Exception ie) {
}
}
};
thread1.start();
thread2.start();
return is;
}
/**
* Insert the OutputStream for into OdfPackage. An existing file will be
* replaced.
*
* @param packagePath
* - relative filePath where the DOM tree should be inserted as
* XML file
* @return outputstream for the data of the file to be stored in package
* @throws java.lang.Exception
* when the DOM tree could not be inserted
*/
public OutputStream insertOutputStream(String packagePath) throws Exception {
return insertOutputStream(packagePath, null);
}
/**
* Insert the OutputStream - to be filled after method - when stream is
* closed into OdfPackage. An existing file will be replaced.
*
* @param packagePath
* - relative filePath where the DOM tree should be inserted as
* XML file
* @param mediaType
* - media type of stream
* @return outputstream for the data of the file to be stored in package
* @throws java.lang.Exception
* when the DOM tree could not be inserted
*/
public OutputStream insertOutputStream(String packagePath, String mediaType)
throws Exception {
packagePath = ensureValidPackagePath(packagePath);
final String fPath = packagePath;
final OdfFileEntry fFileEntry = getFileEntry(packagePath);
final String fMediaType = mediaType;
ByteArrayOutputStream baos = new ByteArrayOutputStream() {
@Override
public void close() {
try {
byte[] data = this.toByteArray();
if (fMediaType == null || fMediaType.length() == 0)
insert(data, fPath, fFileEntry == null ? null
: fFileEntry.getMediaType());
else
insert(data, fPath, fMediaType);
super.close();
} catch (Exception ex) {
mLog.log(Level.SEVERE, null, ex);
}
}
};
return baos;
}
// /**
// * get an InputStream with a specific filePath from the package.
// *
// * @throws IllegalArgumentException if sub-content is not XML
// */
// public InputStream getInputStream(String filePath) throws Exception {
// return mZipFile.getInputStream(mZipFile.getEntry(filePath));
// // OdfPackageStream stream = new OdfPackageStream(this, filePath);
// // return stream;
// }
public void remove(String packagePath) {
HashMap<String, OdfFileEntry> manifestEntries = getManifestEntries();
if (mManifestList != null && mManifestList.contains(packagePath))
mManifestList.remove(packagePath);
if (manifestEntries != null && manifestEntries.containsKey(packagePath))
manifestEntries.remove(packagePath);
if (mZipEntries != null && mZipEntries.containsKey(packagePath))
mZipEntries.remove(packagePath);
if (mTempFiles != null && mTempFiles.containsKey(packagePath))
mTempFiles.remove(packagePath).delete();
if (mPackageEntries != null && mPackageEntries.contains(packagePath))
mPackageEntries.remove(packagePath);
}
/**
* Checks if the given reference is a reference, which points outside the
* ODF package
*
* @param fileRef
* the file reference to be checked
* @return true if the reference is an package external reference
*/
public static boolean isExternalReference(String fileRef) {
// if the fileReference is...
return fileRef.startsWith(TWO_DOTS) // an external relative filePath
|| fileRef.startsWith(SLASH) // or absolute filePath
|| fileRef.contains(COLON); // or absolute IRI
}
/**
* Get Temp Directory. Create new temp directory on demand and register it
* for removal by garbage collector
*/
private File getTempDir() throws Exception {
if (mTempDir == null) {
mTempDir = newTempOdfDirectory("ODF", mTempDirParent);
mFinalize = new OdfFinalizablePackage(mTempDir);
}
return mTempDir;
}
/**
* Encoded XML Attributes
*/
private String encodeXMLAttributes(String s) {
return s.replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
}
private class ManifestContentHandler implements ContentHandler {
private OdfFileEntry fileEntry;
private EncryptionData encryptionData;
/**
* Receive an object for locating the origin of SAX document events.
*/
@Override
public void setDocumentLocator(Locator locator) {
}
/**
* Receive notification of the beginning of a document.
*/
@Override
public void startDocument() throws SAXException {
mManifestList = new LinkedList<>();
mManifestEntries = new HashMap<>();
}
/**
* Receive notification of the end of a document.
*/
@Override
public void endDocument() throws SAXException {
}
/**
* Begin the scope of a prefix-URI Namespace mapping.
*/
@Override
public void startPrefixMapping(String prefix, String uri)
throws SAXException {
}
/**
* End the scope of a prefix-URI mapping.
*/
@Override
public void endPrefixMapping(String prefix) throws SAXException {
}
/**
* Receive notification of the beginning of an element.
*/
@Override
public void startElement(String namespaceURI, String localName,
String qName, Attributes atts) throws SAXException {
switch (localName) {
case "file-entry":
fileEntry = new OdfFileEntry();
fileEntry.setPath(atts.getValue("manifest:full-path"));
fileEntry.setMediaType(atts.getValue("manifest:media-type"));
try {
if (atts.getValue("manifest:size") != null)
fileEntry.setSize(Integer.parseInt(atts
.getValue("manifest:size")));
} catch (NumberFormatException nfe) {
throw new SAXException("not a number: "
+ atts.getValue("manifest:size"));
}
fileEntry.setVersion(atts.getValue("manifest:version"));
break;
case "encryption-data":
encryptionData = new EncryptionData();
if (fileEntry != null) {
encryptionData.setChecksumType(atts
.getValue("manifest:checksum-type"));
encryptionData.setChecksum(atts
.getValue("manifest:checksum"));
fileEntry.setEncryptionData(encryptionData);
}
break;
case "algorithm":
Algorithm algorithm = new Algorithm();
algorithm.setName(atts.getValue("manifest:algorithm-name"));
algorithm.setInitializationVector(atts
.getValue("manifest:initialization-vector"));
if (encryptionData != null)
encryptionData.setAlgorithm(algorithm);
break;
case "key-derivation":
KeyDerivation keyDerivation = new KeyDerivation();
keyDerivation.setName(atts
.getValue("manifest:key-derivation-name"));
keyDerivation.setSalt(atts.getValue("manifest:salt"));
try {
if (atts.getValue("manifest:iteration-count") != null)
keyDerivation.setIterationCount(Integer.parseInt(atts
.getValue("manifest:iteration-count")));
} catch (NumberFormatException nfe) {
throw new SAXException("not a number: "
+ atts.getValue("manifest:iteration-count"));
}
if (encryptionData != null)
encryptionData.setKeyDerivation(keyDerivation);
break;
}
}
/**
* Receive notification of the end of an element.
*/
@Override
public void endElement(String namespaceURI, String localName,
String qName) throws SAXException {
if (localName.equals("file-entry")) {
if (fileEntry.getPath() != null)
getManifestEntries().put(fileEntry.getPath(),
fileEntry);
mManifestList.add(fileEntry.getPath());
fileEntry = null;
} else if (localName.equals("encryption-data"))
encryptionData = null;
}
/**
* Receive notification of character data.
*/
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
}
/**
* Receive notification of ignorable whitespace in element content.
*/
@Override
public void ignorableWhitespace(char[] ch, int start, int length)
throws SAXException {
}
/**
* Receive notification of a processing instruction.
*/
@Override
public void processingInstruction(String target, String data)
throws SAXException {
}
/**
* Receive notification of a skipped entity.
*/
@Override
public void skippedEntity(String name) throws SAXException {
}
}
/**
* resolve external entities
*/
private class Resolver implements EntityResolver, URIResolver {
/**
* Resolver constructor.
*/
public Resolver() {
}
/**
* Allow the application to resolve external entities.
*
* The Parser will call this method before opening any external entity
* except the top-level document entity (including the external DTD
* subset, external entities referenced within the DTD, and external
* entities referenced within the document element): the application may
* request that the parser resolve the entity itself, that it use an
* alternative URI, or that it use an entirely different input source.
*/
@Override
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException {
// This deactivates the attempt to loadPackage the Math DTD
if (publicId != null
&& publicId
.startsWith("-//OpenOffice.org//DTD Modified W3C MathML"))
return new InputSource(new ByteArrayInputStream(
"<?xml version='1.0' encoding='UTF-8'?>".getBytes()));
if (systemId == null)
return null;
if ((mBaseURI != null) && systemId.startsWith(mBaseURI)) {
if (systemId.equals(mBaseURI))
try {
InputStream in = getInputStream();
InputSource ins = new InputSource(in);
ins.setSystemId(systemId);
return ins;
} catch (Exception e) {
throw new SAXException(e);
}
else if (systemId.length() > mBaseURI.length() + 1)
try (InputStream in = getInputStream(systemId
.substring(mBaseURI.length() + 1))) {
InputSource ins = new InputSource(in);
ins.setSystemId(systemId);
return ins;
} catch (Exception ex) {
mLog.log(Level.SEVERE, null, ex);
}
} else if (systemId.startsWith("resource:/")) {
int i = systemId.indexOf('/');
if ((i > 0) && systemId.length() > i + 1) {
String res = systemId.substring(i + 1);
ClassLoader cl = OdfPackage.class.getClassLoader();
InputStream in = cl.getResourceAsStream(res);
if (in != null) {
InputSource ins = new InputSource(in);
ins.setSystemId(systemId);
return ins;
}
}
} else if (systemId.startsWith("jar:"))
try {
URL url = new URL(systemId);
JarURLConnection jarConn = (JarURLConnection) url
.openConnection();
InputSource ins = new InputSource(jarConn.getInputStream());
ins.setSystemId(systemId);
return ins;
} catch (IOException ex) {
mLog.log(Level.SEVERE, null, ex);
}
return null;
}
@Override
public Source resolve(String href, String base)
throws TransformerException {
try {
URI uri;
if (base != null) {
URI baseuri = new URI(base);
uri = baseuri.resolve(href);
} else
uri = new URI(href);
InputSource ins;
try {
ins = resolveEntity(null, uri.toString());
if (ins == null)
return null;
} catch (Exception e) {
throw new TransformerException(e);
}
InputStream in = ins.getByteStream();
StreamSource src = new StreamSource(in);
src.setSystemId(uri.toString());
return src;
} catch (URISyntaxException use) {
return null;
}
}
}
/**
* Get EntityResolver to be used in XML Parsers which can resolve content
* inside the OdfPackage
*
* @return a SAX EntityResolver
*/
public EntityResolver getEntityResolver() {
if (mResolver == null)
mResolver = new Resolver();
return mResolver;
}
/**
* Get URIResolver to be used in XSL Transformations which can resolve
* content inside the OdfPackage
*
* @return a TraX Resolver
*/
public URIResolver getURIResolver() {
if (mResolver == null)
mResolver = new Resolver();
return mResolver;
}
private static String getBaseURIFromFile(File file) throws Exception {
String baseURI = file.getCanonicalFile().toURI().toString();
if (File.separatorChar == '\\')
baseURI = baseURI.replaceAll("\\\\", SLASH);
return baseURI;
}
private class ZipHelper implements AutoCloseable {
private ZipFile mZipFile = null;
private byte[] mZipBuffer = null;
public ZipHelper(ZipFile zipFile) {
mZipFile = zipFile;
mZipBuffer = null;
}
public ZipHelper(byte[] buffer) {
mZipBuffer = buffer;
mZipFile = null;
}
public Enumeration<? extends ZipEntry> entries() throws IOException {
if (mZipFile != null)
return mZipFile.entries();
Vector<ZipEntry> list = new Vector<>();
ZipInputStream inputStream = new ZipInputStream(
new ByteArrayInputStream(mZipBuffer));
ZipEntry zipEntry = inputStream.getNextEntry();
while (zipEntry != null) {
list.add(zipEntry);
zipEntry = inputStream.getNextEntry();
}
inputStream.close();
return list.elements();
}
@SuppressWarnings("resource")
public InputStream getInputStream(ZipEntry entry) throws IOException {
if (mZipFile != null)
return mZipFile.getInputStream(entry);
ZipInputStream inputStream = new ZipInputStream(
new ByteArrayInputStream(mZipBuffer));
ZipEntry zipEntry = inputStream.getNextEntry();
while (zipEntry != null) {
if (zipEntry.getName().equalsIgnoreCase(entry.getName()))
return readAsInputStream(inputStream);
zipEntry = inputStream.getNextEntry();
}
return null;
}
private InputStream readAsInputStream(ZipInputStream inputStream)
throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
while (true) {
int r = inputStream.read(buf);
if (r < 0)
break;
outputStream.write(buf, 0, r);
}
inputStream.close();
return new ByteArrayInputStream(outputStream.toByteArray());
}
@Override
public void close() throws IOException {
if (mZipFile != null)
mZipFile.close();
else
mZipBuffer = null;
}
}
}