/* DigiDoc4J library * * This software is released under either the GNU Library General Public * License (see LICENSE.LGPL). * * Note that the only valid version of the LGPL license as far as this * project is concerned is the original GNU Library General Public License * Version 2.1, February 1999 */ package org.digidoc4j.impl.bdoc.asic; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.BOMInputStream; import org.apache.commons.lang.StringUtils; import org.digidoc4j.Configuration; import org.digidoc4j.DataFile; import org.digidoc4j.exceptions.DuplicateDataFileException; import org.digidoc4j.exceptions.TechnicalException; import org.digidoc4j.exceptions.UnsupportedFormatException; import org.digidoc4j.impl.StreamDocument; import org.digidoc4j.impl.bdoc.manifest.ManifestEntry; import org.digidoc4j.impl.bdoc.manifest.ManifestParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.europa.esig.dss.DSSDocument; import eu.europa.esig.dss.InMemoryDocument; import eu.europa.esig.dss.MimeType; public abstract class AsicContainerParser { private final static Logger logger = LoggerFactory.getLogger(AsicContainerParser.class); //Matches META-INF/*signatures*.xml where the last * is a number private static final String SIGNATURES_FILE_REGEX = "META-INF/(.*)signatures(.*).xml"; private static final Pattern SIGNATURE_FILE_ENDING_PATTERN = Pattern.compile("(\\d+).xml"); public static final String MANIFEST = "META-INF/manifest.xml"; private AsicParseResult parseResult = new AsicParseResult(); private List<DSSDocument> signatures = new ArrayList<>(); private LinkedHashMap<String, DataFile> dataFiles = new LinkedHashMap<>(); private List<DSSDocument> detachedContents = new ArrayList<>(); private Integer currentSignatureFileIndex; private String mimeType; private String zipFileComment; private List<AsicEntry> asicEntries = new ArrayList<>(); private Map<String, ManifestEntry> manifestFileItems = Collections.emptyMap(); private ManifestParser manifestParser; private boolean storeDataFilesOnlyInMemory; private long maxDataFileCachedInBytes; protected AsicContainerParser(Configuration configuration) { storeDataFilesOnlyInMemory = configuration.storeDataFilesOnlyInMemory(); maxDataFileCachedInBytes = configuration.getMaxDataFileCachedInBytes(); } public AsicParseResult read() { parseContainer(); validateParseResult(); populateParseResult(); return parseResult; } protected abstract void parseContainer(); protected abstract void extractManifest(ZipEntry entry); protected abstract InputStream getZipEntryInputStream(ZipEntry entry); protected void parseManifestEntry(DSSDocument manifestFile) { logger.debug("Parsing manifest"); manifestParser = new ManifestParser(manifestFile); manifestFileItems = manifestParser.getManifestFileItems(); } protected void parseEntry(ZipEntry entry) { String entryName = entry.getName(); logger.debug("Paring zip entry " + entryName + " with comment: " + entry.getComment()); if (isMimeType(entryName)) { extractMimeType(entry); } else if (isManifest(entryName)) { extractManifest(entry); } else if (isSignaturesFile(entryName)) { determineCurrentSignatureFileIndex(entryName); extractSignature(entry); } else if (isDataFile(entryName)) { extractDataFile(entry); } else { extractAsicEntry(entry); } } private void extractMimeType(ZipEntry entry) { try { InputStream zipFileInputStream = getZipEntryInputStream(entry); BOMInputStream bomInputStream = new BOMInputStream(zipFileInputStream); DSSDocument document = new InMemoryDocument(bomInputStream); mimeType = StringUtils.trim(IOUtils.toString(getDocumentBytes(document), "UTF-8")); extractAsicEntry(entry, document); } catch (IOException e) { logger.error("Error parsing container mime type: " + e.getMessage()); throw new TechnicalException("Error parsing container mime type: " + e.getMessage(), e); } } private void extractSignature(ZipEntry entry) { logger.debug("Extracting signature"); InputStream zipFileInputStream = getZipEntryInputStream(entry); String fileName = entry.getName(); InMemoryDocument document = new InMemoryDocument(zipFileInputStream, fileName); signatures.add(document); extractSignatureAsicEntry(entry, document); } private void extractDataFile(ZipEntry entry) { logger.debug("Extracting data file"); String fileName = entry.getName(); validateDataFile(fileName); DSSDocument document = extractStreamDocument(entry); DataFile dataFile = new AsicDataFile(document); dataFiles.put(fileName, dataFile); detachedContents.add(document); extractAsicEntry(entry, document); } private DSSDocument extractStreamDocument(ZipEntry entry) { logger.debug("Zip entry size is " + entry.getSize() + " bytes"); InputStream zipFileInputStream = getZipEntryInputStream(entry); String fileName = entry.getName(); String mimeType = getDataFileMimeType(fileName); MimeType mimeTypeCode = MimeType.fromMimeTypeString(mimeType); DSSDocument document; if(storeDataFilesOnlyInMemory || entry.getSize() <= maxDataFileCachedInBytes) { document = new InMemoryDocument(zipFileInputStream, fileName, mimeTypeCode); } else { document = new StreamDocument(zipFileInputStream, fileName, mimeTypeCode); } return document; } protected AsicEntry extractAsicEntry(ZipEntry entry) { logger.debug("Extracting asic entry"); DSSDocument document = extractStreamDocument(entry); return extractAsicEntry(entry, document); } private AsicEntry extractAsicEntry(ZipEntry zipEntry, DSSDocument document) { AsicEntry asicEntry = new AsicEntry(zipEntry); asicEntry.setContent(document); asicEntries.add(asicEntry); return asicEntry; } private void extractSignatureAsicEntry(ZipEntry entry, DSSDocument document) { AsicEntry asicEntry = extractAsicEntry(entry, document); asicEntry.setSignature(true); } protected String getDataFileMimeType(String fileName) { if (manifestFileItems.containsKey(fileName)) { ManifestEntry manifestEntry = manifestFileItems.get(fileName); return manifestEntry.getMimeType(); } else { MimeType mimeType = MimeType.fromFileName(fileName); return mimeType.getMimeTypeString(); } } private void validateParseResult() { if (!StringUtils.equalsIgnoreCase(MimeType.ASICE.getMimeTypeString(), mimeType)) { logger.error("Container mime type is not " + MimeType.ASICE.getMimeTypeString() + " but is " + mimeType); throw new UnsupportedFormatException("Container mime type is not " + MimeType.ASICE.getMimeTypeString() + " but is " + mimeType); } } private void validateDataFile(String fileName) { if (dataFiles.containsKey(fileName)) { logger.error("Container contains duplicate data file: " + fileName); throw new DuplicateDataFileException("Container contains duplicate data file: " + fileName); } } private void populateParseResult() { Collection<DataFile> files = dataFiles.values(); parseResult.setDataFiles(new ArrayList<>(files)); parseResult.setSignatures(signatures); parseResult.setCurrentUsedSignatureFileIndex(currentSignatureFileIndex); parseResult.setDetachedContents(detachedContents); parseResult.setManifestParser(manifestParser); parseResult.setZipFileComment(zipFileComment); parseResult.setAsicEntries(asicEntries); } private boolean isMimeType(String entryName) { return StringUtils.equalsIgnoreCase("mimetype", entryName); } private boolean isDataFile(String entryName) { return !entryName.startsWith("META-INF/") && !isMimeType(entryName); } private boolean isManifest(String entryName) { return StringUtils.equalsIgnoreCase(MANIFEST, entryName); } private boolean isSignaturesFile(String entryName) { return entryName.matches(SIGNATURES_FILE_REGEX); } private void determineCurrentSignatureFileIndex(String entryName) { Matcher fileEndingMatcher = SIGNATURE_FILE_ENDING_PATTERN.matcher(entryName); boolean fileEndingFound = fileEndingMatcher.find(); if (fileEndingFound) { String fileEnding = fileEndingMatcher.group(); String indexNumber = fileEnding.replace(".xml", ""); int fileIndex = Integer.parseInt(indexNumber); if (currentSignatureFileIndex == null || currentSignatureFileIndex <= fileIndex) { currentSignatureFileIndex = fileIndex; } } } private byte[] getDocumentBytes(DSSDocument document) { try { return IOUtils.toByteArray(document.openStream()); } catch (IOException e) { logger.error("Error getting document content: " + e.getMessage()); throw new TechnicalException("Error getting document content: " + e.getMessage(), e); } } void setZipFileComment(String zipFileComment) { this.zipFileComment = zipFileComment; } LinkedHashMap<String, DataFile> getDataFiles() { return dataFiles; } }