All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.poi.poifs.filesystem.NPOIFSFileSystem Maven / Gradle / Ivy

There is a newer version: 5.2.5
Show newest version

/* ====================================================================
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You 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

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
==================================================================== */


package org.apache.poi.poifs.filesystem;

import java.io.ByteArrayInputStream;
import java.io.Closeable;
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.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.poi.EmptyFileException;
import org.apache.poi.poifs.common.POIFSBigBlockSize;
import org.apache.poi.poifs.common.POIFSConstants;
import org.apache.poi.poifs.dev.POIFSViewable;
import org.apache.poi.poifs.nio.ByteArrayBackedDataSource;
import org.apache.poi.poifs.nio.DataSource;
import org.apache.poi.poifs.nio.FileBackedDataSource;
import org.apache.poi.poifs.property.DirectoryProperty;
import org.apache.poi.poifs.property.DocumentProperty;
import org.apache.poi.poifs.property.NPropertyTable;
import org.apache.poi.poifs.storage.BATBlock;
import org.apache.poi.poifs.storage.BATBlock.BATBlockAndIndex;
import org.apache.poi.poifs.storage.BlockAllocationTableReader;
import org.apache.poi.poifs.storage.BlockAllocationTableWriter;
import org.apache.poi.poifs.storage.HeaderBlock;
import org.apache.poi.poifs.storage.HeaderBlockWriter;
import org.apache.poi.util.CloseIgnoringInputStream;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Internal;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.Removal;

/**
 * 

This is the main class of the POIFS system; it manages the entire * life cycle of the filesystem.

*

This is the new NIO version, which uses less memory

*/ public class NPOIFSFileSystem extends BlockStore implements POIFSViewable, Closeable { private static final POILogger LOG = POILogFactory.getLogger(NPOIFSFileSystem.class); /** * Convenience method for clients that want to avoid the auto-close behaviour of the constructor. */ public static InputStream createNonClosingInputStream(InputStream is) { return new CloseIgnoringInputStream(is); } private NPOIFSMiniStore _mini_store; private NPropertyTable _property_table; private List _xbat_blocks; private List _bat_blocks; private HeaderBlock _header; private DirectoryNode _root; private DataSource _data; /** * What big block size the file uses. Most files * use 512 bytes, but a few use 4096 */ private POIFSBigBlockSize bigBlockSize = POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS; private NPOIFSFileSystem(boolean newFS) { _header = new HeaderBlock(bigBlockSize); _property_table = new NPropertyTable(_header); _mini_store = new NPOIFSMiniStore(this, _property_table.getRoot(), new ArrayList(), _header); _xbat_blocks = new ArrayList(); _bat_blocks = new ArrayList(); _root = null; if(newFS) { // Data needs to initially hold just the header block, // a single bat block, and an empty properties section _data = new ByteArrayBackedDataSource(new byte[bigBlockSize.getBigBlockSize()*3]); } } /** * Constructor, intended for writing */ public NPOIFSFileSystem() { this(true); // Reserve block 0 for the start of the Properties Table // Create a single empty BAT, at pop that at offset 1 _header.setBATCount(1); _header.setBATArray(new int[] { 1 }); BATBlock bb = BATBlock.createEmptyBATBlock(bigBlockSize, false); bb.setOurBlockIndex(1); _bat_blocks.add(bb); setNextBlock(0, POIFSConstants.END_OF_CHAIN); setNextBlock(1, POIFSConstants.FAT_SECTOR_BLOCK); _property_table.setStartBlock(0); } /** *

Creates a POIFSFileSystem from a File. This uses less memory than * creating from an InputStream. The File will be opened read-only

* *

Note that with this constructor, you will need to call {@link #close()} * when you're done to have the underlying file closed, as the file is * kept open during normal operation to read the data out.

* * @param file the File from which to read the data * * @exception IOException on errors reading, or on invalid data */ public NPOIFSFileSystem(File file) throws IOException { this(file, true); } /** *

Creates a POIFSFileSystem from a File. This uses less memory than * creating from an InputStream.

* *

Note that with this constructor, you will need to call {@link #close()} * when you're done to have the underlying file closed, as the file is * kept open during normal operation to read the data out.

* * @param file the File from which to read or read/write the data * @param readOnly whether the POIFileSystem will only be used in read-only mode * * @exception IOException on errors reading, or on invalid data */ public NPOIFSFileSystem(File file, boolean readOnly) throws IOException { this(null, file, readOnly, true); } /** *

Creates a POIFSFileSystem from an open FileChannel. This uses * less memory than creating from an InputStream. The stream will * be used in read-only mode.

* *

Note that with this constructor, you will need to call {@link #close()} * when you're done to have the underlying Channel closed, as the channel is * kept open during normal operation to read the data out.

* * @param channel the FileChannel from which to read the data * * @exception IOException on errors reading, or on invalid data */ public NPOIFSFileSystem(FileChannel channel) throws IOException { this(channel, true); } /** *

Creates a POIFSFileSystem from an open FileChannel. This uses * less memory than creating from an InputStream.

* *

Note that with this constructor, you will need to call {@link #close()} * when you're done to have the underlying Channel closed, as the channel is * kept open during normal operation to read the data out.

* * @param channel the FileChannel from which to read or read/write the data * @param readOnly whether the POIFileSystem will only be used in read-only mode * * @exception IOException on errors reading, or on invalid data */ public NPOIFSFileSystem(FileChannel channel, boolean readOnly) throws IOException { this(channel, null, readOnly, false); } private NPOIFSFileSystem(FileChannel channel, File srcFile, boolean readOnly, boolean closeChannelOnError) throws IOException { this(false); try { // Initialize the datasource if (srcFile != null) { if (srcFile.length() == 0) throw new EmptyFileException(); FileBackedDataSource d = new FileBackedDataSource(srcFile, readOnly); channel = d.getChannel(); _data = d; } else { _data = new FileBackedDataSource(channel, readOnly); } // Get the header ByteBuffer headerBuffer = ByteBuffer.allocate(POIFSConstants.SMALLER_BIG_BLOCK_SIZE); IOUtils.readFully(channel, headerBuffer); // Have the header processed _header = new HeaderBlock(headerBuffer); // Now process the various entries readCoreContents(); } catch(IOException e) { // Until we upgrade to Java 7, and can do a MultiCatch, we // need to keep these two catch blocks in sync on their cleanup if (closeChannelOnError && channel != null) { channel.close(); channel = null; } throw e; } catch(RuntimeException e) { // Comes from Iterators etc. // TODO Decide if we can handle these better whilst // still sticking to the iterator contract if (closeChannelOnError && channel != null) { channel.close(); channel = null; } throw e; } } /** * Create a POIFSFileSystem from an InputStream. Normally the stream is read until * EOF. The stream is always closed.

* * Some streams are usable after reaching EOF (typically those that return true * for markSupported()). In the unlikely case that the caller has such a stream * and needs to use it after this constructor completes, a work around is to wrap the * stream in order to trap the close() call. A convenience method ( * createNonClosingInputStream()) has been provided for this purpose: *

     * InputStream wrappedStream = POIFSFileSystem.createNonClosingInputStream(is);
     * HSSFWorkbook wb = new HSSFWorkbook(wrappedStream);
     * is.reset();
     * doSomethingElse(is);
     * 
* Note also the special case of ByteArrayInputStream for which the close() * method does nothing. *
     * ByteArrayInputStream bais = ...
     * HSSFWorkbook wb = new HSSFWorkbook(bais); // calls bais.close() !
     * bais.reset(); // no problem
     * doSomethingElse(bais);
     * 
* * @param stream the InputStream from which to read the data * * @exception IOException on errors reading, or on invalid data */ public NPOIFSFileSystem(InputStream stream) throws IOException { this(false); ReadableByteChannel channel = null; boolean success = false; try { // Turn our InputStream into something NIO based channel = Channels.newChannel(stream); // Get the header ByteBuffer headerBuffer = ByteBuffer.allocate(POIFSConstants.SMALLER_BIG_BLOCK_SIZE); IOUtils.readFully(channel, headerBuffer); // Have the header processed _header = new HeaderBlock(headerBuffer); // Sanity check the block count BlockAllocationTableReader.sanityCheckBlockCount(_header.getBATCount()); // We need to buffer the whole file into memory when // working with an InputStream. // The max possible size is when each BAT block entry is used long maxSize = BATBlock.calculateMaximumSize(_header); if (maxSize > Integer.MAX_VALUE) { throw new IllegalArgumentException("Unable read a >2gb file via an InputStream"); } ByteBuffer data = ByteBuffer.allocate((int)maxSize); // Copy in the header headerBuffer.position(0); data.put(headerBuffer); data.position(headerBuffer.capacity()); // Now read the rest of the stream IOUtils.readFully(channel, data); success = true; // Turn it into a DataSource _data = new ByteArrayBackedDataSource(data.array(), data.position()); } finally { // As per the constructor contract, always close the stream if(channel != null) channel.close(); closeInputStream(stream, success); } // Now process the various entries readCoreContents(); } /** * @param stream the stream to be closed * @param success false if an exception is currently being thrown in the calling method */ private void closeInputStream(InputStream stream, boolean success) { try { stream.close(); } catch (IOException e) { if(success) { throw new RuntimeException(e); } // else not success? Try block did not complete normally // just print stack trace and leave original ex to be thrown LOG.log(POILogger.ERROR, "can't close input stream", e); } } /** * Checks that the supplied InputStream (which MUST * support mark and reset) has a POIFS (OLE2) header at the start of it. * If unsure if your InputStream does support mark / reset, * use {@link FileMagic#prepareToCheckMagic(InputStream)} to wrap it and make * sure to always use that, and not the original! * * After the method call, the InputStream is at the * same position as of the time of entering the method. * * @param inp An InputStream which supports mark/reset * * @deprecated in 3.17-beta2, use {@link FileMagic#valueOf(InputStream)} == {@link FileMagic#OLE2} instead */ @Deprecated @Removal(version="4.0") public static boolean hasPOIFSHeader(InputStream inp) throws IOException { return FileMagic.valueOf(inp) == FileMagic.OLE2; } /** * Checks if the supplied first 8 bytes of a stream / file * has a POIFS (OLE2) header. * * @deprecated in 3.17-beta2, use {@link FileMagic#valueOf(InputStream)} == {@link FileMagic#OLE2} instead */ @Deprecated @Removal(version="4.0") public static boolean hasPOIFSHeader(byte[] header8Bytes) { try { return hasPOIFSHeader(new ByteArrayInputStream(header8Bytes)); } catch (IOException e) { throw new RuntimeException("invalid header check", e); } } /** * Read and process the PropertiesTable and the * FAT / XFAT blocks, so that we're ready to * work with the file */ private void readCoreContents() throws IOException { // Grab the block size bigBlockSize = _header.getBigBlockSize(); // Each block should only ever be used by one of the // FAT, XFAT or Property Table. Ensure it does ChainLoopDetector loopDetector = getChainLoopDetector(); // Read the FAT blocks for(int fatAt : _header.getBATArray()) { readBAT(fatAt, loopDetector); } // Work out how many FAT blocks remain in the XFATs int remainingFATs = _header.getBATCount() - _header.getBATArray().length; // Now read the XFAT blocks, and the FATs within them BATBlock xfat; int nextAt = _header.getXBATIndex(); for(int i=0; i<_header.getXBATCount(); i++) { loopDetector.claim(nextAt); ByteBuffer fatData = getBlockAt(nextAt); xfat = BATBlock.createBATBlock(bigBlockSize, fatData); xfat.setOurBlockIndex(nextAt); nextAt = xfat.getValueAt(bigBlockSize.getXBATEntriesPerBlock()); _xbat_blocks.add(xfat); // Process all the (used) FATs from this XFAT int xbatFATs = Math.min(remainingFATs, bigBlockSize.getXBATEntriesPerBlock()); for(int j=0; j sbats = new ArrayList(); _mini_store = new NPOIFSMiniStore(this, _property_table.getRoot(), sbats, _header); nextAt = _header.getSBATStart(); for(int i=0; i<_header.getSBATCount() && nextAt != POIFSConstants.END_OF_CHAIN; i++) { loopDetector.claim(nextAt); ByteBuffer fatData = getBlockAt(nextAt); sfat = BATBlock.createBATBlock(bigBlockSize, fatData); sfat.setOurBlockIndex(nextAt); sbats.add(sfat); nextAt = getNextBlock(nextAt); } } private void readBAT(int batAt, ChainLoopDetector loopDetector) throws IOException { loopDetector.claim(batAt); ByteBuffer fatData = getBlockAt(batAt); BATBlock bat = BATBlock.createBATBlock(bigBlockSize, fatData); bat.setOurBlockIndex(batAt); _bat_blocks.add(bat); } private BATBlock createBAT(int offset, boolean isBAT) throws IOException { // Create a new BATBlock BATBlock newBAT = BATBlock.createEmptyBATBlock(bigBlockSize, !isBAT); newBAT.setOurBlockIndex(offset); // Ensure there's a spot in the file for it ByteBuffer buffer = ByteBuffer.allocate(bigBlockSize.getBigBlockSize()); int writeTo = (1+offset) * bigBlockSize.getBigBlockSize(); // Header isn't in BATs _data.write(buffer, writeTo); // All done return newBAT; } /** * Load the block at the given offset. */ @Override protected ByteBuffer getBlockAt(final int offset) throws IOException { // The header block doesn't count, so add one long blockWanted = offset + 1L; long startAt = blockWanted * bigBlockSize.getBigBlockSize(); try { return _data.read(bigBlockSize.getBigBlockSize(), startAt); } catch (IndexOutOfBoundsException e) { IndexOutOfBoundsException wrapped = new IndexOutOfBoundsException("Block " + offset + " not found"); wrapped.initCause(e); throw wrapped; } } /** * Load the block at the given offset, * extending the file if needed */ @Override protected ByteBuffer createBlockIfNeeded(final int offset) throws IOException { try { return getBlockAt(offset); } catch(IndexOutOfBoundsException e) { // The header block doesn't count, so add one long startAt = (offset+1L) * bigBlockSize.getBigBlockSize(); // Allocate and write ByteBuffer buffer = ByteBuffer.allocate(getBigBlockSize()); _data.write(buffer, startAt); // Retrieve the properly backed block return getBlockAt(offset); } } /** * Returns the BATBlock that handles the specified offset, * and the relative index within it */ @Override protected BATBlockAndIndex getBATBlockAndIndex(final int offset) { return BATBlock.getBATBlockAndIndex( offset, _header, _bat_blocks ); } /** * Works out what block follows the specified one. */ @Override protected int getNextBlock(final int offset) { BATBlockAndIndex bai = getBATBlockAndIndex(offset); return bai.getBlock().getValueAt( bai.getIndex() ); } /** * Changes the record of what block follows the specified one. */ @Override protected void setNextBlock(final int offset, final int nextBlock) { BATBlockAndIndex bai = getBATBlockAndIndex(offset); bai.getBlock().setValueAt( bai.getIndex(), nextBlock ); } /** * Finds a free block, and returns its offset. * This method will extend the file if needed, and if doing * so, allocate new FAT blocks to address the extra space. */ @Override protected int getFreeBlock() throws IOException { int numSectors = bigBlockSize.getBATEntriesPerBlock(); // First up, do we have any spare ones? int offset = 0; for (BATBlock bat : _bat_blocks) { if(bat.hasFreeSectors()) { // Claim one of them and return it for(int j=0; j= 109) { // Needs to come from an XBAT BATBlock xbat = null; for(BATBlock x : _xbat_blocks) { if(x.hasFreeSectors()) { xbat = x; break; } } if(xbat == null) { // Oh joy, we need a new XBAT too... xbat = createBAT(offset+1, false); // Allocate our new BAT as the first block in the XBAT xbat.setValueAt(0, offset); // And allocate the XBAT in the BAT bat.setValueAt(1, POIFSConstants.DIFAT_SECTOR_BLOCK); // Will go one place higher as XBAT added in offset++; // Chain it if(_xbat_blocks.size() == 0) { _header.setXBATStart(offset); } else { _xbat_blocks.get(_xbat_blocks.size()-1).setValueAt( bigBlockSize.getXBATEntriesPerBlock(), offset ); } _xbat_blocks.add(xbat); _header.setXBATCount(_xbat_blocks.size()); } else { // Allocate our BAT in the existing XBAT with space for(int i=0; i getViewableIterator() { if (!preferArray()) { return (( POIFSViewable ) getRoot()).getViewableIterator(); } return Collections.emptyList().iterator(); } /** * Give viewers a hint as to whether to call getViewableArray or * getViewableIterator * * @return true if a viewer should call getViewableArray, false if * a viewer should call getViewableIterator */ public boolean preferArray() { return (( POIFSViewable ) getRoot()).preferArray(); } /** * Provides a short description of the object, to be used when a * POIFSViewable object has not provided its contents. * * @return short description */ public String getShortDescription() { return "POIFS FileSystem"; } /* ********** END begin implementation of POIFSViewable ********** */ /** * @return The Big Block size, normally 512 bytes, sometimes 4096 bytes */ public int getBigBlockSize() { return bigBlockSize.getBigBlockSize(); } /** * @return The Big Block size, normally 512 bytes, sometimes 4096 bytes */ public POIFSBigBlockSize getBigBlockSizeDetails() { return bigBlockSize; } @Override protected int getBlockStoreBlockSize() { return getBigBlockSize(); } @Internal public NPropertyTable getPropertyTable() { return _property_table; } @Internal public HeaderBlock getHeaderBlock() { return _header; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy