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

com.pff.PSTNodeInputStream Maven / Gradle / Ivy

There is a newer version: 0.9.3
Show newest version
/*
 * Copyright 2010 Richard Johnson & Orin Eman
 *
 * 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
 *
 * 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 part of java-libpst.
 *
 * java-libpst is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * java-libpst is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with java-libpst.  If not, see .
 *
 */

package com.pff;

import java.io.*;
import java.util.*;

/**
 * this input stream basically "maps" an input stream on top of the random access file
 * @author richard
 */
public class PSTNodeInputStream extends InputStream {

	private RandomAccessFile in;
	private PSTFile pstFile;
	private LinkedList skipPoints = new LinkedList();
	private LinkedList indexItems = new LinkedList();
	private int currentBlock = 0;
	private long currentLocation = 0;

	private byte[] allData = null;

	private long length = 0;

	private boolean encrypted = false;

	PSTNodeInputStream(PSTFile pstFile, byte[] attachmentData) {
		this.allData = attachmentData;
		this.length = this.allData.length;
		this.encrypted = pstFile.getEncryptionType() == PSTFile.ENCRYPTION_TYPE_COMPRESSIBLE;
		this.currentBlock = 0;
		this.currentLocation = 0;
	}

	PSTNodeInputStream(PSTFile pstFile, PSTDescriptorItem descriptorItem)
			throws IOException, PSTException
	{
		this.in = pstFile.getFileHandle();
		this.pstFile = pstFile;
		this.encrypted = pstFile.getEncryptionType() == PSTFile.ENCRYPTION_TYPE_COMPRESSIBLE;

		// we want to get the first block of data and see what we are dealing with
		OffsetIndexItem offsetItem = pstFile.getOffsetIndexNode(descriptorItem.offsetIndexIdentifier);
		loadFromOffsetItem(offsetItem);
		this.currentBlock = 0;
		this.currentLocation = 0;

	}

	PSTNodeInputStream(PSTFile pstFile, OffsetIndexItem offsetItem)
			throws IOException, PSTException
	{
		this.in = pstFile.getFileHandle();
		this.pstFile = pstFile;
		this.encrypted = pstFile.getEncryptionType() == PSTFile.ENCRYPTION_TYPE_COMPRESSIBLE;
		loadFromOffsetItem(offsetItem);
		this.currentBlock = 0;
		this.currentLocation = 0;
	}

	private void loadFromOffsetItem(OffsetIndexItem offsetItem)
			throws IOException, PSTException
	{
		boolean bInternal = (offsetItem.indexIdentifier & 0x02) != 0;

		in.seek(offsetItem.fileOffset);
		byte[] data = new byte[offsetItem.size];
		in.read(data);

		if ( bInternal ) {
			// All internal blocks are at least 8 bytes long...
			if ( offsetItem.size < 8 ) {
				throw new PSTException("Invalid internal block size");
			}

			if ( data[0] == 1 )
			{
				bInternal = false;
				// we are a block, or xxblock
				length = PSTObject.convertLittleEndianBytesToLong(data, 4, 8);
				// go through all of the blocks and create skip points.
				this.getBlockSkipPoints(data);
				return;
			}
		}

		// (Internal blocks aren't compressed)
		if (bInternal) {
			this.encrypted = false;
		}
		this.allData = data;
		this.length = this.allData.length;

	}

	public boolean isEncrypted() {
		return this.encrypted;
	}

	private void getBlockSkipPoints(byte[] data)
			throws IOException, PSTException
	{
		if (data[0] != 0x1) {
			throw new PSTException("Unable to process XBlock, incorrect identifier");
		}

		int numberOfEntries = (int)PSTObject.convertLittleEndianBytesToLong(data, 2, 4);

		int arraySize = 8;
		if (this.pstFile.getPSTFileType() == PSTFile.PST_TYPE_ANSI) {
			arraySize = 4;
		}
		if (data[1] == 0x2) {
			// XXBlock
			int offset = 8;
			for (int x = 0; x < numberOfEntries; x++) {
				long bid = PSTObject.convertLittleEndianBytesToLong(data, offset, offset+arraySize);
				bid &= 0xfffffffe;
				// get the details in this block and
				OffsetIndexItem offsetItem = this.pstFile.getOffsetIndexNode(bid);
				in.seek(offsetItem.fileOffset);
				byte[] blockData = new byte[offsetItem.size];
				in.read(blockData);
				this.getBlockSkipPoints(blockData);
				offset += arraySize;
			}
		} else if (data[1] == 0x1) {
			// normal XBlock
			int offset = 8;
			for (int x = 0; x < numberOfEntries; x++) {
				long bid = PSTObject.convertLittleEndianBytesToLong(data, offset, offset+arraySize);
				bid &= 0xfffffffe;
				// get the details in this block and add it to the list
				OffsetIndexItem offsetItem = pstFile.getOffsetIndexNode(bid);
				this.indexItems.add(offsetItem);
				this.skipPoints.add(this.currentLocation);
				this.currentLocation += offsetItem.size;
				offset += arraySize;
			}
		}
	}

	public long length() {
		return this.length;
	}

	@Override
	public int read()
			throws IOException
	{

		// first deal with items < 8K and we have all the data already
		if (this.allData != null) {
			if (this.currentLocation == this.length) {
				// EOF
				return -1;
			}
			int value = this.allData[(int)this.currentLocation] & 0xFF;
			this.currentLocation++;
			if (this.encrypted) {
				value = PSTObject.compEnc[value];
			}
			return value;
		}

		OffsetIndexItem item = this.indexItems.get(this.currentBlock);
		long skipPoint = this.skipPoints.get(currentBlock);
		if (this.currentLocation+1 > skipPoint+item.size) {
			// got to move to the next block
			this.currentBlock++;

			if (this.currentBlock >= this.indexItems.size()) {
				return -1;
			}

			item = this.indexItems.get(this.currentBlock);
			skipPoint = this.skipPoints.get(currentBlock);
		}

		// get the next byte.
		long pos = (item.fileOffset + (this.currentLocation - skipPoint));
		if (in.getFilePointer()  != pos) {
			in.seek(pos);
		}

		int output = in.read();
		if (output < 0) {
			return -1;
		}
		if (this.encrypted) {
			output = PSTObject.compEnc[output];
		}

		this.currentLocation++;

		return output;
	}

	private int totalLoopCount = 0;

	/**
	 * Read a block from the input stream.
	 * Recommended block size = 8176 (size used internally by PSTs)
	 * @param output
	 * @return
	 * @throws IOException
	 */
	@Override
	public int read(byte[] output)
			throws IOException
	{
		// this method is implemented in an attempt to make things a bit faster than the byte-by-byte read() crap above.
		// it's tricky 'cause we have to copy blocks from a few different areas.


		if (this.currentLocation == this.length) {
			// EOF
			return -1;
		}

		// first deal with the small stuff
		if (this.allData != null) {
			int bytesRemaining = (int)(this.length - this.currentLocation);
			if (output.length >= bytesRemaining) {
				System.arraycopy(this.allData, (int)this.currentLocation, output, 0, bytesRemaining);
				if (this.encrypted) {
					PSTObject.decode(output);
				}
				this.currentLocation += bytesRemaining; // should be = to this.length
				return bytesRemaining;
			} else {
				System.arraycopy(this.allData, (int)this.currentLocation, output, 0, output.length);
				if (this.encrypted) {
					PSTObject.decode(output);
				}
				this.currentLocation += output.length;
				return output.length;
			}
		}

		boolean filled = false;
		int totalBytesFilled = 0;
		// while we still need to fill the array
		while (!filled) {

			// fill up the output from where we are
			// get the current block, either to the end, or until the length of the output
			OffsetIndexItem offset = this.indexItems.get(this.currentBlock);
			long skipPoint = this.skipPoints.get(currentBlock);
			int currentPosInBlock = (int)(this.currentLocation - skipPoint);
			in.seek(offset.fileOffset + currentPosInBlock);

			long nextSkipPoint = skipPoint + offset.size;
			int bytesRemaining = (output.length - totalBytesFilled);
			// if the total bytes remaining if going to take us past our size
			if (bytesRemaining > ((int)(this.length - this.currentLocation))) {
				// we only have so much to give
				bytesRemaining = (int)(this.length - this.currentLocation);
			}

			if (nextSkipPoint >= this.currentLocation + bytesRemaining) {
				// we can fill the output with the rest of our current block!
				byte[] chunk = new byte[bytesRemaining];
				in.read(chunk);

				System.arraycopy(chunk, 0, output, totalBytesFilled, bytesRemaining);
				totalBytesFilled += bytesRemaining;
				// we are done!
				filled = true;
				this.currentLocation += bytesRemaining;
			} else {
				// we need to read out a whole chunk and keep going
				int bytesToRead = offset.size - currentPosInBlock;
				byte[] chunk = new byte[bytesToRead];
				in.read(chunk);
				System.arraycopy(chunk, 0, output, totalBytesFilled, bytesToRead);
				totalBytesFilled += bytesToRead;
				this.currentBlock++;
				this.currentLocation += bytesToRead;
			}
			totalLoopCount++;
		}

		// decode the array if required
		if (this.encrypted) {
			PSTObject.decode(output);
		}

		// fill up our chunk
		// move to the next chunk
		return totalBytesFilled;
	}

	@Override
	public int read(byte[] output, int offset, int length)
			throws IOException
	{
		if (this.currentLocation == this.length) {
			// EOF
			return -1;
		}

		if (output.length < length) {
			length = output.length;
		}

		byte[] buf = new byte[length];
		int lengthRead = this.read(buf);

		System.arraycopy(buf, 0, output, offset, lengthRead);

		return lengthRead;
	}


	@Override
	public void reset() {
		this.currentBlock = 0;
		this.currentLocation = 0;
	}

	@Override
	public boolean markSupported() {
		return false;
	}

	/**
	 * Get the offsets (block positions) used in the array
	 * @return
	 */
	public Long[] getBlockOffsets() {
		if (this.skipPoints.size() == 0) {
			Long[] output = new Long[1];
			output[0]=this.length;
			return output;
		} else {
			Long[] output = new Long[this.skipPoints.size()];
			for (int x =0 ; x < output.length; x++) {
				output[x] = new Long(this.skipPoints.get(x) + this.indexItems.get(x).size);
			}
			return output;
		}
	}

	/*
	public int[] getBlockOffsetsInts() {
		int[] out = new int[this.skipPoints.size()];
		for (int x = 0; x < this.skipPoints.size(); x++) {
			out[x] = this.skipPoints.get(x).intValue();
		}
		return out;
	}
	 *
	 */

	public void seek(long location)
			throws IOException, PSTException
	{
		// not past the end!
		if (location > this.length) {
			throw new PSTException("Unable to seek past end of item! size = "+this.length+", seeking to:"+location);
		}

		// are we already there?
		if (this.currentLocation == location) {
			return;
		}

		// get us to the right block
		long skipPoint = 0;
		this.currentBlock = 0;
		if (this.allData == null) {
			skipPoint = this.skipPoints.get(this.currentBlock + 1);
			while (location >= skipPoint) {
				this.currentBlock++;
				// is this the last block?
				if (this.currentBlock == this.skipPoints.size()-1) {
					// that's all folks
					break;
				} else {
					skipPoint = this.skipPoints.get(this.currentBlock + 1);
				}
			}
		}

		// now move us to the right position in there
		this.currentLocation = location;

		long blockStart = 0;
		if (this.allData == null) {
			blockStart = this.indexItems.get(currentBlock).fileOffset;
		}
		long newFilePos = blockStart + (location - skipPoint);
		this.in.seek(newFilePos);

	}

	public long seekAndReadLong(long location, int bytes)
			throws IOException, PSTException
	{
		this.seek(location);
		byte[] buffer = new byte[bytes];
		this.read(buffer);
		return PSTObject.convertLittleEndianBytesToLong(buffer);
	}

	public PSTFile getPSTFile() {
		return this.pstFile;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy