From 8e8805758c2424573e8c82ffe4eaac55ec6bea8b Mon Sep 17 00:00:00 2001 From: casimirenslip Date: Mon, 29 Apr 2013 12:34:58 +0200 Subject: [PATCH 1/3] - refactoring - changes to manage a stream --- com/pff/DescriptorIndexNode.java | 100 --- com/pff/LZFu.java | 127 --- com/pff/OffsetIndexItem.java | 71 -- com/pff/PSTActivity.java | 145 ---- com/pff/PSTAppointment.java | 193 ----- com/pff/PSTAppointmentException.java | 389 ---------- com/pff/PSTAppointmentRecurrence.java | 310 -------- com/pff/PSTAttachment.java | 255 ------- com/pff/PSTContact.java | 864 --------------------- com/pff/PSTDescriptorItem.java | 122 --- com/pff/PSTException.java | 53 -- com/pff/PSTFile.java | 912 ---------------------- com/pff/PSTFolder.java | 405 ---------- com/pff/PSTMessage.java | 1017 ------------------------- com/pff/PSTMessageStore.java | 91 --- com/pff/PSTNodeInputStream.java | 450 ----------- com/pff/PSTObject.java | 812 -------------------- com/pff/PSTRecipient.java | 153 ---- com/pff/PSTRss.java | 121 --- com/pff/PSTTable.java | 269 ------- com/pff/PSTTable7C.java | 421 ---------- com/pff/PSTTable7CItem.java | 46 -- com/pff/PSTTableBC.java | 204 ----- com/pff/PSTTableBCItem.java | 46 -- com/pff/PSTTableItem.java | 195 ----- com/pff/PSTTask.java | 191 ----- com/pff/PSTTimeZone.java | 266 ------- example/Test.java | 23 +- example/TestGui.java | 95 ++- 29 files changed, 81 insertions(+), 8265 deletions(-) delete mode 100644 com/pff/DescriptorIndexNode.java delete mode 100644 com/pff/LZFu.java delete mode 100644 com/pff/OffsetIndexItem.java delete mode 100644 com/pff/PSTActivity.java delete mode 100644 com/pff/PSTAppointment.java delete mode 100644 com/pff/PSTAppointmentException.java delete mode 100644 com/pff/PSTAppointmentRecurrence.java delete mode 100644 com/pff/PSTAttachment.java delete mode 100644 com/pff/PSTContact.java delete mode 100644 com/pff/PSTDescriptorItem.java delete mode 100644 com/pff/PSTException.java delete mode 100644 com/pff/PSTFile.java delete mode 100644 com/pff/PSTFolder.java delete mode 100644 com/pff/PSTMessage.java delete mode 100644 com/pff/PSTMessageStore.java delete mode 100644 com/pff/PSTNodeInputStream.java delete mode 100644 com/pff/PSTObject.java delete mode 100644 com/pff/PSTRecipient.java delete mode 100644 com/pff/PSTRss.java delete mode 100644 com/pff/PSTTable.java delete mode 100644 com/pff/PSTTable7C.java delete mode 100644 com/pff/PSTTable7CItem.java delete mode 100644 com/pff/PSTTableBC.java delete mode 100644 com/pff/PSTTableBCItem.java delete mode 100644 com/pff/PSTTableItem.java delete mode 100644 com/pff/PSTTask.java delete mode 100644 com/pff/PSTTimeZone.java diff --git a/com/pff/DescriptorIndexNode.java b/com/pff/DescriptorIndexNode.java deleted file mode 100644 index 8e97b76..0000000 --- a/com/pff/DescriptorIndexNode.java +++ /dev/null @@ -1,100 +0,0 @@ -/** - * 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.IOException; - -/** - * DescriptorIndexNode is a leaf item from the Descriptor index b-tree - * It is like a pointer to an element in the PST file, everything has one... - * @author Richard Johnson - */ -public class DescriptorIndexNode { - public int descriptorIdentifier; - public long dataOffsetIndexIdentifier; - public long localDescriptorsOffsetIndexIdentifier; - public int parentDescriptorIndexIdentifier; - public int itemType; - - //PSTFile.PSTFileBlock dataBlock = null; - - /** - * parse the data out into something meaningful - * @param data - */ - DescriptorIndexNode(byte[] data, int pstFileType) { - // parse it out - // first 4 bytes - if (pstFileType == PSTFile.PST_TYPE_ANSI) { - descriptorIdentifier = (int)PSTObject.convertLittleEndianBytesToLong(data, 0, 4); - dataOffsetIndexIdentifier = (int)PSTObject.convertLittleEndianBytesToLong(data, 4, 8); - localDescriptorsOffsetIndexIdentifier = (int)PSTObject.convertLittleEndianBytesToLong(data, 8, 12); - parentDescriptorIndexIdentifier = (int)PSTObject.convertLittleEndianBytesToLong(data, 12, 16); - //itemType = (int)PSTObject.convertLittleEndianBytesToLong(data, 28, 32); - } else { - descriptorIdentifier = (int)PSTObject.convertLittleEndianBytesToLong(data, 0, 4); - dataOffsetIndexIdentifier = (int)PSTObject.convertLittleEndianBytesToLong(data, 8, 16); - localDescriptorsOffsetIndexIdentifier = (int)PSTObject.convertLittleEndianBytesToLong(data, 16, 24); - parentDescriptorIndexIdentifier = (int)PSTObject.convertLittleEndianBytesToLong(data, 24, 28); - itemType = (int)PSTObject.convertLittleEndianBytesToLong(data, 28, 32); - } - } - - /* - void readData(PSTFile file) - throws IOException, PSTException - { - if ( dataBlock == null ) { - dataBlock = file.readLeaf(dataOffsetIndexIdentifier); - } - } - * - */ - - PSTNodeInputStream getNodeInputStream(PSTFile pstFile) - throws IOException, PSTException - { - return new PSTNodeInputStream(pstFile,pstFile.getOffsetIndexNode(dataOffsetIndexIdentifier)); - } - - public String toString() { - - return "DescriptorIndexNode\n" + - "Descriptor Identifier: "+descriptorIdentifier+" (0x"+Long.toHexString(descriptorIdentifier)+")\n"+ - "Data offset identifier: "+dataOffsetIndexIdentifier+" (0x"+Long.toHexString(dataOffsetIndexIdentifier)+")\n"+ - "Local descriptors offset index identifier: "+localDescriptorsOffsetIndexIdentifier+" (0x"+Long.toHexString(localDescriptorsOffsetIndexIdentifier)+")\n"+ - "Parent Descriptor Index Identifier: "+parentDescriptorIndexIdentifier+" (0x"+Long.toHexString(parentDescriptorIndexIdentifier)+")\n"+ - "Item Type: "+itemType+" (0x"+Long.toHexString(itemType)+")"; - } -} diff --git a/com/pff/LZFu.java b/com/pff/LZFu.java deleted file mode 100644 index c5a573b..0000000 --- a/com/pff/LZFu.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.UnsupportedEncodingException; - -/** - * An implementation of the LZFu algorithm to decompress RTF content - * @author Richard Johnson - */ -public class LZFu { - - public static final String LZFU_HEADER = "{\\rtf1\\ansi\\mac\\deff0\\deftab720{\\fonttbl;}{\\f0\\fnil \\froman \\fswiss \\fmodern \\fscript \\fdecor MS Sans SerifSymbolArialTimes New RomanCourier{\\colortbl\\red0\\green0\\blue0\n\r\\par \\pard\\plain\\f0\\fs20\\b\\i\\u\\tab\\tx"; - - public static String decode(byte[] data) - throws PSTException - { - - @SuppressWarnings("unused") - int compressedSize = (int)PSTObject.convertLittleEndianBytesToLong(data, 0, 4); - int uncompressedSize = (int)PSTObject.convertLittleEndianBytesToLong(data, 4, 8); - int compressionSig = (int)PSTObject.convertLittleEndianBytesToLong(data, 8, 12); - @SuppressWarnings("unused") - int compressedCRC = (int)PSTObject.convertLittleEndianBytesToLong(data, 12, 16); - - if (compressionSig == 0x75465a4c) { - // we are compressed... - byte[] output = new byte[uncompressedSize]; - int outputPosition = 0; - byte[] lzBuffer = new byte[4096]; - // preload our buffer. - try { - byte[] bytes = LZFU_HEADER.getBytes("US-ASCII"); - System.arraycopy(bytes, 0, lzBuffer, 0, LZFU_HEADER.length()); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - int bufferPosition = LZFU_HEADER.length(); - int currentDataPosition = 16; - - // next byte is the flags, - while (currentDataPosition < data.length - 2 && outputPosition < output.length) { - int flags = data[currentDataPosition++] & 0xFF; - for (int x = 0; x < 8 && outputPosition < output.length; x++) { - boolean isRef = ((flags & 1) == 1); - flags >>= 1; - if (isRef) { - // get the starting point for the buffer and the - // length to read - int refOffsetOrig = data[currentDataPosition++] & 0xFF; - int refSizeOrig = data[currentDataPosition++] & 0xFF; - int refOffset = (refOffsetOrig << 4) | (refSizeOrig >>> 4); - int refSize = (refSizeOrig & 0xF) + 2; - //refOffset &= 0xFFF; - try { - // copy the data from the buffer - int index = refOffset; - for (int y = 0; y < refSize && outputPosition < output.length; y++) { - output[outputPosition++] = lzBuffer[index]; - lzBuffer[bufferPosition] = lzBuffer[index]; - bufferPosition++; - bufferPosition %= 4096; - ++index; - index %= 4096; - } - } catch ( Exception e ) { - e.printStackTrace(); - } - - } else { - // copy the byte over - lzBuffer[bufferPosition] = data[currentDataPosition]; - bufferPosition++; - bufferPosition %= 4096; - output[outputPosition++] = data[currentDataPosition++]; - } - } - } - - if ( outputPosition != uncompressedSize ) { - throw new PSTException(String.format("Error decompressing RTF! Expected %d bytes, got %d bytes\n", uncompressedSize, outputPosition)); - } - return new String(output).trim(); - - } else if (compressionSig == 0x414c454d) { - // we are not compressed! - // just return the rest of the contents as a string - byte[] output = new byte[data.length-16]; - System.arraycopy(data, 16, output, 0, data.length-16); - return new String(output).trim(); - } - - return ""; - } -} diff --git a/com/pff/OffsetIndexItem.java b/com/pff/OffsetIndexItem.java deleted file mode 100644 index 76f1808..0000000 --- a/com/pff/OffsetIndexItem.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * 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; - -/** - * OffsetIndexItem is a leaf item from the Offset index b-tree - * Only really used internally to get the file offset for items - * @author Richard Johnson - */ -class OffsetIndexItem { - long indexIdentifier; - long fileOffset; - int size; - long cRef; - - OffsetIndexItem(byte[] data, int pstFileType) { - if (pstFileType == PSTFile.PST_TYPE_ANSI) { - indexIdentifier = PSTObject.convertLittleEndianBytesToLong(data, 0, 4); - fileOffset = PSTObject.convertLittleEndianBytesToLong(data, 4, 8); - size = (int)PSTObject.convertLittleEndianBytesToLong(data, 8, 10); - cRef = (int)PSTObject.convertLittleEndianBytesToLong(data, 10, 12); - } else { - indexIdentifier = PSTObject.convertLittleEndianBytesToLong(data, 0, 8); - fileOffset = PSTObject.convertLittleEndianBytesToLong(data, 8, 16); - size = (int)PSTObject.convertLittleEndianBytesToLong(data, 16, 18); - cRef = (int)PSTObject.convertLittleEndianBytesToLong(data, 16, 18); - } - //System.out.println("Data size: "+data.length); - - } - - @Override - public String toString() { - return "OffsetIndexItem\n"+ - "Index Identifier: "+indexIdentifier+" (0x"+Long.toHexString(indexIdentifier)+")\n"+ - "File Offset: "+fileOffset+" (0x"+Long.toHexString(fileOffset)+")\n"+ - "cRef: "+cRef+" (0x"+Long.toHexString(cRef)+" bin:"+Long.toBinaryString(cRef)+")\n"+ - "Size: "+size+" (0x"+Long.toHexString(size)+")"; - } -} diff --git a/com/pff/PSTActivity.java b/com/pff/PSTActivity.java deleted file mode 100644 index d4cae3d..0000000 --- a/com/pff/PSTActivity.java +++ /dev/null @@ -1,145 +0,0 @@ -/** - * 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.IOException; -import java.util.HashMap; -import java.util.Date; - -/** - * PSTActivity represents Journal entries - * @author Richard Johnson - */ -public class PSTActivity extends PSTMessage { - - /** - * @param theFile - * @param descriptorIndexNode - * @throws PSTException - * @throws IOException - */ - public PSTActivity(PSTFile theFile, DescriptorIndexNode descriptorIndexNode) - throws PSTException, IOException { - super(theFile, descriptorIndexNode); - } - - /** - * @param theFile - * @param folderIndexNode - * @param table - * @param localDescriptorItems - */ - public PSTActivity(PSTFile theFile, DescriptorIndexNode folderIndexNode, - PSTTableBC table, - HashMap localDescriptorItems) { - super(theFile, folderIndexNode, table, localDescriptorItems); - } - - /** - * Type - */ - public String getLogType() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008700, PSTFile.PSETID_Log)); - } - /** - * Start - */ - public Date getLogStart() { - return getDateItem(pstFile.getNameToIdMapItem(0x00008706, PSTFile.PSETID_Log)); - } - /** - * Duration - */ - public int getLogDuration() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008707, PSTFile.PSETID_Log)); - } - /** - * End - */ - public Date getLogEnd() { - return getDateItem(pstFile.getNameToIdMapItem(0x00008708, PSTFile.PSETID_Log)); - } - /** - * LogFlags - */ - public int getLogFlags() { - return getIntItem(pstFile.getNameToIdMapItem(0x0000870c, PSTFile.PSETID_Log)); - } - /** - * DocPrinted - */ - public boolean isDocumentPrinted() { - return (getBooleanItem(pstFile.getNameToIdMapItem(0x0000870e, PSTFile.PSETID_Log))); - } - /** - * DocSaved - */ - public boolean isDocumentSaved() { - return (getBooleanItem(pstFile.getNameToIdMapItem(0x0000870f, PSTFile.PSETID_Log))); - } - /** - * DocRouted - */ - public boolean isDocumentRouted() { - return (getBooleanItem(pstFile.getNameToIdMapItem(0x00008710, PSTFile.PSETID_Log))); - } - /** - * DocPosted - */ - public boolean isDocumentPosted() { - return (getBooleanItem(pstFile.getNameToIdMapItem(0x00008711, PSTFile.PSETID_Log))); - } - /** - * Type Description - */ - public String getLogTypeDesc() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008712, PSTFile.PSETID_Log)); - } - - public String toString() { - return - "Type ASCII or Unicode string: "+ getLogType() + "\n" + - "Start Filetime: "+ getLogStart() + "\n" + - "Duration Integer 32-bit signed: "+ getLogDuration() + "\n" + - "End Filetime: "+ getLogEnd() + "\n" + - "LogFlags Integer 32-bit signed: "+ getLogFlags() + "\n" + - "DocPrinted Boolean: "+ isDocumentPrinted() + "\n" + - "DocSaved Boolean: "+ isDocumentSaved() + "\n" + - "DocRouted Boolean: "+ isDocumentRouted() + "\n" + - "DocPosted Boolean: "+ isDocumentPosted() + "\n" + - "TypeDescription ASCII or Unicode string: "+ getLogTypeDesc(); - - } - -} diff --git a/com/pff/PSTAppointment.java b/com/pff/PSTAppointment.java deleted file mode 100644 index b82216e..0000000 --- a/com/pff/PSTAppointment.java +++ /dev/null @@ -1,193 +0,0 @@ -/** - * 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.IOException; -import java.util.Date; -import java.util.HashMap; - -/** - * PSTAppointment is for Calendar items - * @author Richard Johnson - */ -public class PSTAppointment extends PSTMessage { - - PSTAppointment(PSTFile theFile, DescriptorIndexNode descriptorIndexNode) - throws PSTException, IOException - { - super(theFile, descriptorIndexNode); - } - - PSTAppointment(PSTFile theFile, DescriptorIndexNode folderIndexNode, PSTTableBC table, HashMap localDescriptorItems) - { - super(theFile, folderIndexNode, table, localDescriptorItems); - } - - public boolean getSendAsICAL() { - return (getBooleanItem(pstFile.getNameToIdMapItem(0x00008200, PSTFile.PSETID_Appointment))); - } - public int getBusyStatus() - { - return getIntItem(pstFile.getNameToIdMapItem(0x00008205, PSTFile.PSETID_Appointment)); - } - public boolean getShowAsBusy() { - return getBusyStatus() == 2; - } - public String getLocation() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008208, PSTFile.PSETID_Appointment)); - } - public Date getStartTime() { - return getDateItem(pstFile.getNameToIdMapItem(0x0000820d, PSTFile.PSETID_Appointment)); - } - public PSTTimeZone getStartTimeZone() { - return getTimeZoneItem(pstFile.getNameToIdMapItem(0x0000825e, PSTFile.PSETID_Appointment)); - } - public Date getEndTime() { - return getDateItem(pstFile.getNameToIdMapItem(0x0000820e, PSTFile.PSETID_Appointment)); - } - public PSTTimeZone getEndTimeZone() { - return getTimeZoneItem(pstFile.getNameToIdMapItem(0x0000825f, PSTFile.PSETID_Appointment)); - } - - public PSTTimeZone getRecurrenceTimeZone() { - String desc = getStringItem(pstFile.getNameToIdMapItem(0x00008234, PSTFile.PSETID_Appointment)); - if ( desc!= null && desc.length() != 0 ) { - byte[] tzData = getBinaryItem(pstFile.getNameToIdMapItem(0x00008233, PSTFile.PSETID_Appointment)); - if ( tzData != null && tzData.length != 0 ) { - return new PSTTimeZone(desc, tzData); - } - } - return null; - } - public int getDuration() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008213, PSTFile.PSETID_Appointment)); - } - public int getColor() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008214, PSTFile.PSETID_Appointment)); - } - public boolean getSubType() { - return (getIntItem(pstFile.getNameToIdMapItem(0x00008215, PSTFile.PSETID_Appointment)) != 0); - } - public int getMeetingStatus() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008217, PSTFile.PSETID_Appointment)); - } - public int getResponseStatus() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008218, PSTFile.PSETID_Appointment)); - } - public boolean isRecurring() { - return getBooleanItem(pstFile.getNameToIdMapItem(0x00008223, PSTFile.PSETID_Appointment)); - } - public Date getRecurrenceBase() { - return getDateItem(pstFile.getNameToIdMapItem(0x00008228, PSTFile.PSETID_Appointment)); - } - public int getRecurrenceType() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008231, PSTFile.PSETID_Appointment)); - } - public String getRecurrencePattern() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008232, PSTFile.PSETID_Appointment)); - } - public byte[] getRecurrenceStructure() { - return getBinaryItem(pstFile.getNameToIdMapItem(0x00008216, PSTFile.PSETID_Appointment)); - } - public byte[] getTimezone() { - return getBinaryItem(pstFile.getNameToIdMapItem(0x00008233, PSTFile.PSETID_Appointment)); - } - public String getAllAttendees() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008238, PSTFile.PSETID_Appointment)); - } - public String getToAttendees() { - return getStringItem(pstFile.getNameToIdMapItem(0x0000823b, PSTFile.PSETID_Appointment)); - } - public String getCCAttendees() { - return getStringItem(pstFile.getNameToIdMapItem(0x0000823c, PSTFile.PSETID_Appointment)); - } - public int getAppointmentSequence() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008201, PSTFile.PSETID_Appointment)); - } - - // online meeting properties - public boolean isOnlineMeeting() { - return (getBooleanItem(pstFile.getNameToIdMapItem(0x00008240, PSTFile.PSETID_Appointment))); - } - public int getNetMeetingType() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008241, PSTFile.PSETID_Appointment)); - } - public String getNetMeetingServer() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008242, PSTFile.PSETID_Appointment)); - } - public String getNetMeetingOrganizerAlias() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008243, PSTFile.PSETID_Appointment)); - } - public boolean getNetMeetingAutostart() { - return (getIntItem(pstFile.getNameToIdMapItem(0x00008245, PSTFile.PSETID_Appointment)) != 0); - } - public boolean getConferenceServerAllowExternal() { - return (getBooleanItem(pstFile.getNameToIdMapItem(0x00008246, PSTFile.PSETID_Appointment))); - } - public String getNetMeetingDocumentPathName() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008247, PSTFile.PSETID_Appointment)); - } - public String getNetShowURL() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008248, PSTFile.PSETID_Appointment)); - } - public Date getAttendeeCriticalChange() { - return getDateItem(pstFile.getNameToIdMapItem(0x00000001, PSTFile.PSETID_Meeting)); - } - public Date getOwnerCriticalChange() { - return getDateItem(pstFile.getNameToIdMapItem(0x0000001a, PSTFile.PSETID_Meeting)); - } - public String getConferenceServerPassword() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008249, PSTFile.PSETID_Appointment)); - } - - public boolean getAppointmentCounterProposal() { - return (getBooleanItem(pstFile.getNameToIdMapItem(0x00008257, PSTFile.PSETID_Appointment))); - } - - public boolean isSilent() { - return (getBooleanItem(pstFile.getNameToIdMapItem(0x00000004, PSTFile.PSETID_Meeting))); - } - - public String getRequiredAttendees() { - return getStringItem(this.pstFile.getNameToIdMapItem(0x00000006, PSTFile.PSETID_Meeting)); - } - - public int getLocaleId() { - return getIntItem(0x3ff1); - } - - public byte[] getGlobalObjectId() { - return getBinaryItem(pstFile.getNameToIdMapItem(0x00000003, PSTFile.PSETID_Meeting)); - } -} diff --git a/com/pff/PSTAppointmentException.java b/com/pff/PSTAppointmentException.java deleted file mode 100644 index 9230070..0000000 --- a/com/pff/PSTAppointmentException.java +++ /dev/null @@ -1,389 +0,0 @@ -/** - * 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.UnsupportedEncodingException; -import java.util.Calendar; -import java.util.Date; - -/** - * Class containing information on exceptions to a recurring appointment - * @author Orin Eman - * - * - */ -public class PSTAppointmentException { - - // Access methods - return the value from the exception if - // OverrideFlags say it's present, otherwise the value from the appointment. - public String getSubject() { - if ( (OverrideFlags & 0x0001) != 0 ) { - try { - return new String(WideCharSubject, "UTF-16LE"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - } - - return this.appt.getSubject(); - } - - - public int getMeetingType() - { - if ( (OverrideFlags & 0x0002) != 0 ) { - return MeetingType; - } - - return appt.getMeetingStatus(); - } - - - public int getReminderDelta() { - if ( (OverrideFlags & 0x0004) != 0 ) { - return ReminderDelta; - } - - return appt.getReminderDelta(); - } - - - public boolean getReminderSet() { - if ( (OverrideFlags & 0x0008) != 0 ) { - return ReminderSet; - } - - return appt.getReminderSet(); - } - - - public String getLocation() { - if ( (OverrideFlags & 0x0010) != 0 ) { - try { - return new String(WideCharLocation, "UTF-16LE"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - } - - return appt.getLocation(); - } - - - public int getBusyStatus() { - if ( (OverrideFlags & 0x0020) != 0 ) { - return BusyStatus; - } - - return appt.getBusyStatus(); - } - - - public boolean getSubType() { - if ( (OverrideFlags & 0x0080) != 0 ) { - return SubType; - } - - return appt.getSubType(); - } - - public String getDescription() - { - if ( embeddedMessage != null ) { - return embeddedMessage.getBodyPrefix(); - } - - return null; - } - - public Date getDTStamp() { - Date ret = null; - if ( embeddedMessage != null ) { - ret = embeddedMessage.getOwnerCriticalChange(); - } - - if ( ret == null ) { - // Use current date/time - Calendar c = Calendar.getInstance(PSTTimeZone.utcTimeZone); - ret = c.getTime(); - } - - return ret; - } - - public int getStartDateTime() { - return StartDateTime; - } - - public int getEndDateTime() { - return EndDateTime; - } - - public int getOriginalStartDate() { - return OriginalStartDate; - } - - public int getAppointmentSequence(int def) { - if ( embeddedMessage == null ) { - return def; - } - return embeddedMessage.getAppointmentSequence(); - } - - public int getImportance(int def) { - if ( embeddedMessage == null ) { - return def; - } - return embeddedMessage.getImportance(); - } - - public byte[] getSubjectBytes() { - if ( (OverrideFlags & 0x0010) != 0 ) { - return Subject; - } - - return null; - } - - public byte[] getLocationBytes() { - if ( (OverrideFlags & 0x0010) != 0 ) { - return Location; - } - - return null; - } - - public boolean attachmentsPresent() { - if ( (OverrideFlags & 0x0040) != 0 && - Attachment == 0x00000001 ) - { - return true; - } - - return false; - } - - public boolean embeddedMessagePresent() { - return embeddedMessage != null; - } - - // - // Allow access to an embedded message for - // properties that don't have access methods here. - // - public PSTAppointment getEmbeddedMessage() { - return embeddedMessage; - } - - PSTAppointmentException(byte[] recurrencePattern, - int offset, - int writerVersion2, - PSTAppointment appt) { - this.writerVersion2 = writerVersion2; - int initialOffset = offset; - this.appt = appt; - embeddedMessage = null; - - StartDateTime = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - EndDateTime = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - OriginalStartDate = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - OverrideFlags = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); - offset += 2; - - if ( (OverrideFlags & ARO_SUBJECT) != 0 ) { - //@SuppressWarnings("unused") - //short SubjectLength = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); - offset += 2; - short SubjectLength2 = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); - offset += 2; - Subject = new byte[SubjectLength2]; - System.arraycopy(recurrencePattern, offset, Subject, 0, SubjectLength2); - offset += SubjectLength2; - } - - if ( (OverrideFlags & ARO_MEETINGTYPE) != 0 ) { - MeetingType = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - } - - if ( (OverrideFlags & ARO_REMINDERDELTA) != 0 ) { - ReminderDelta = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - } - - if ( (OverrideFlags & ARO_REMINDER) != 0 ) { - ReminderSet = ((int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4) != 0); - offset += 4; - } - - if ( (OverrideFlags & ARO_LOCATION) != 0 ) { - //@SuppressWarnings("unused") - //short LocationLength = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); - offset += 2; - short LocationLength2 = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); - offset += 2; - Location = new byte[LocationLength2]; - System.arraycopy(recurrencePattern, offset, Location, 0, LocationLength2); - offset += LocationLength2; - } - - if ( (OverrideFlags & ARO_BUSYSTATUS) != 0 ) { - BusyStatus = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - } - - if ( (OverrideFlags & ARO_ATTACHMENT) != 0 ) { - Attachment = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - } - - if ( (OverrideFlags & ARO_SUBTYPE) != 0 ) { - SubType = ((int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4) != 0); - offset += 4; - } - - length = offset - initialOffset; - } - - void ExtendedException(byte[] recurrencePattern, int offset) { - int initialOffset = offset; - - if ( writerVersion2 >= 0x00003009 ) { - int ChangeHighlightSize = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - ChangeHighlightValue = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += ChangeHighlightSize; - } - - int ReservedBlockEESize = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4 + ReservedBlockEESize; - - // See http://msdn.microsoft.com/en-us/library/cc979209(office.12).aspx - if ( (OverrideFlags & (ARO_SUBJECT|ARO_LOCATION)) != 0 ) { - // Same as regular Exception structure? - StartDateTime = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - EndDateTime = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - OriginalStartDate = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - } - - if ( (OverrideFlags & ARO_SUBJECT) != 0 ) { - WideCharSubjectLength = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); - offset += 2; - WideCharSubject = new byte[WideCharSubjectLength * 2]; - System.arraycopy(recurrencePattern, offset, WideCharSubject, 0, WideCharSubject.length); - offset += WideCharSubject.length; -/* - try { - String subject = new String(WideCharSubject, "UTF-16LE"); - System.out.printf("Exception Subject: %s\n", subject); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } -/**/ - } - - if ( (OverrideFlags & ARO_LOCATION) != 0 ) { - WideCharLocationLength = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); - offset += 2; - WideCharLocation = new byte[WideCharLocationLength*2]; - System.arraycopy(recurrencePattern, offset, WideCharLocation, 0, WideCharLocation.length); - offset += WideCharLocation.length; - } - - // See http://msdn.microsoft.com/en-us/library/cc979209(office.12).aspx - if ( (OverrideFlags & (ARO_SUBJECT|ARO_LOCATION)) != 0 ) { - ReservedBlockEESize = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4 + ReservedBlockEESize; - } - - extendedLength = offset - initialOffset; - } - - void setEmbeddedMessage(PSTAppointment embeddedMessage) { - this.embeddedMessage = embeddedMessage; - } - - private int writerVersion2; - private int StartDateTime; - private int EndDateTime; - private int OriginalStartDate; - private short OverrideFlags; - private byte[] Subject = null; - private int MeetingType; - private int ReminderDelta; - private boolean ReminderSet; - private byte[] Location = null; - private int BusyStatus; - private int Attachment; - private boolean SubType; -// private int AppointmentColor; // Reserved - don't read from the PST file - @SuppressWarnings("unused") - private int ChangeHighlightValue; - private int WideCharSubjectLength = 0; - private byte[] WideCharSubject = null; - private int WideCharLocationLength = 0; - private byte[] WideCharLocation = null; - private PSTAppointment embeddedMessage = null; - private PSTAppointment appt; - private int length; - private int extendedLength; - - - // Length of this ExceptionInfo structure in the PST file - int getLength() { - return length; - } - - // Length of this ExtendedException structure in the PST file - int getExtendedLength() { - return extendedLength; - } - - - static final short ARO_SUBJECT = 0x0001; - static final short ARO_MEETINGTYPE = 0x0002; - static final short ARO_REMINDERDELTA = 0x0004; - static final short ARO_REMINDER = 0x0008; - static final short ARO_LOCATION = 0x0010; - static final short ARO_BUSYSTATUS = 0x0020; - static final short ARO_ATTACHMENT = 0x0040; - static final short ARO_SUBTYPE = 0x0080; -} diff --git a/com/pff/PSTAppointmentRecurrence.java b/com/pff/PSTAppointmentRecurrence.java deleted file mode 100644 index 4d06c3d..0000000 --- a/com/pff/PSTAppointmentRecurrence.java +++ /dev/null @@ -1,310 +0,0 @@ -/** - * 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.text.SimpleDateFormat; -/**/ - -import java.util.Calendar; -import java.util.Date; -import java.util.SimpleTimeZone; - -/** - * Class containing recurrence information for a recurring appointment - * @author Orin Eman - * - * - */ - -public class PSTAppointmentRecurrence { - - // Access methods - - public short getExceptionCount() { - return ExceptionCount; - } - - public PSTAppointmentException getException(int i) { - if ( i < 0 || i >= ExceptionCount ) { - return null; - } - return Exceptions[i]; - } - - public short getCalendarType() { - return CalendarType; - } - - public short getPatternType() { - return PatternType; - } - - public int getPeriod() { - return Period; - } - - public int getPatternSpecific() { - return PatternSpecific; - } - - public int getFirstDOW() { - return FirstDOW; - } - - public int getPatternSpecificNth() { - return PatternSpecificNth; - } - - public int getFirstDateTime() { - return FirstDateTime; - } - - public int getEndType() { - return EndType; - } - - public int getOccurrenceCount() { - return OccurrenceCount; - } - - public int getEndDate() { - return EndDate; - } - - public int getStartTimeOffset() { - return StartTimeOffset; - } - - public PSTTimeZone getTimeZone() { - return RecurrenceTimeZone; - } - - public int getRecurFrequency() { - return RecurFrequency; - } - - public int getSlidingFlag() { - return SlidingFlag; - } - - public int getStartDate() { - return StartDate; - } - - public int getEndTimeOffset() { - return EndTimeOffset; - } - - public PSTAppointmentRecurrence(byte[] recurrencePattern, PSTAppointment appt, PSTTimeZone tz) { - RecurrenceTimeZone = tz; - SimpleTimeZone stz = RecurrenceTimeZone.getSimpleTimeZone(); - - // Read the structure - RecurFrequency = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 4, 6); - PatternType = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 6, 8); - CalendarType = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 8, 10); - FirstDateTime = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 10, 14); - Period = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 14, 18); - SlidingFlag = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 18, 22); - int offset = 22; - if ( PatternType != 0 ) { - PatternSpecific = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - if ( PatternType == 0x0003 || PatternType == 0x000B ) { - PatternSpecificNth = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - } - } - EndType = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - OccurrenceCount = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - FirstDOW = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - - DeletedInstanceCount = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - DeletedInstanceDates = new Calendar[DeletedInstanceCount]; - for ( int i = 0; i < DeletedInstanceCount; ++i ) { - DeletedInstanceDates[i] = PSTObject.apptTimeToUTC( - (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4), - RecurrenceTimeZone); - offset += 4; -/* - SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); - f.setTimeZone(RecurrenceTimeZone.getSimpleTimeZone()); - System.out.printf("DeletedInstanceDates[%d]: %s\n", i, f.format(DeletedInstanceDates[i].getTime())); -/**/ - } - - ModifiedInstanceCount = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - ModifiedInstanceDates = new Calendar[ModifiedInstanceCount]; - for ( int i = 0; i < ModifiedInstanceCount; ++i ) { - ModifiedInstanceDates[i] = PSTObject.apptTimeToUTC( - (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4), - RecurrenceTimeZone); - offset += 4; -/* - SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); - f.setTimeZone(RecurrenceTimeZone.getSimpleTimeZone()); - System.out.printf("ModifiedInstanceDates[%d]: %s\n", i, f.format(ModifiedInstanceDates[i].getTime())); -/**/ - } - - StartDate = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - EndDate = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4 + 4; // Skip ReaderVersion2 - - writerVersion2 = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - - StartTimeOffset = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - EndTimeOffset = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4; - ExceptionCount = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); - offset += 2; - - // Read exceptions - Exceptions = new PSTAppointmentException[ExceptionCount]; - for ( int i = 0; i < ExceptionCount; ++i ) { - Exceptions[i] = new PSTAppointmentException(recurrencePattern, offset, writerVersion2, appt); - offset += Exceptions[i].getLength(); - } - - if ( (offset + 4) <= recurrencePattern.length ) { - int ReservedBlock1Size = (int)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); - offset += 4 + (ReservedBlock1Size * 4); - } - - // Read extended exception info - for ( int i = 0; i < ExceptionCount; ++i ) { - Exceptions[i].ExtendedException(recurrencePattern, offset); - offset += Exceptions[i].getExtendedLength(); -/* - Calendar c = PSTObject.apptTimeToUTC(Exceptions[i].getStartDateTime(), RecurrenceTimeZone); - System.out.printf("Exception[%d] start: %s\n", i, FormatUTC(c.getTime())); - c = PSTObject.apptTimeToUTC(Exceptions[i].getEndDateTime(), RecurrenceTimeZone); - System.out.printf("Exception[%d] end: %s\n", i, FormatUTC(c.getTime())); - c = PSTObject.apptTimeToUTC(Exceptions[i].getOriginalStartDate(), RecurrenceTimeZone); - System.out.printf("Exception[%d] original start: %s\n", i, FormatUTC(c.getTime())); -/**/ - } - // Ignore any extra data - see http://msdn.microsoft.com/en-us/library/cc979209(office.12).aspx - - // Get attachments, if any - PSTAttachment[] attachments = new PSTAttachment[appt.getNumberOfAttachments()]; - for ( int i = 0; i < attachments.length; ++i ) { - try { - attachments[i] = appt.getAttachment(i); - } catch (Exception e) { - e.printStackTrace(); - attachments[i] = null; - } - } - - PSTAppointment embeddedMessage = null; - for ( int i = 0; i < ExceptionCount; ++i ) { - try { - // Match up an attachment to this exception... - for ( int iAttachment = 0; iAttachment < attachments.length; ++iAttachment ) { - if ( attachments[iAttachment] != null ) { - PSTMessage message = attachments[iAttachment].getEmbeddedPSTMessage(); - if ( !(message instanceof PSTAppointment) ) { - continue; - } - embeddedMessage = (PSTAppointment)message; - Date replaceTime = embeddedMessage.getRecurrenceBase(); -/* - SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); - f.setTimeZone(stz); - System.out.printf("Attachment[%d] time: %s\n", iAttachment, f.format(replaceTime)); -/**/ - Calendar c = Calendar.getInstance(stz); - c.setTime(replaceTime); - if ( c.get(Calendar.YEAR) == ModifiedInstanceDates[i].get(Calendar.YEAR) && - c.get(Calendar.MONTH) == ModifiedInstanceDates[i].get(Calendar.MONTH) && - c.get(Calendar.YEAR) == ModifiedInstanceDates[i].get(Calendar.YEAR) ) - { -/* System.out.println("\tEmbedded Message matched"); /**/ - - Exceptions[i].setEmbeddedMessage(embeddedMessage); - break; - } - } - } - } catch ( Exception e ) { - e.printStackTrace(); - } - } - - attachments = null; - } - -/* - private String FormatUTC(Date date) { - SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); - f.setTimeZone(PSTTimeZone.utcTimeZone); - return f.format(date); - } -/**/ - - private short RecurFrequency; - private short PatternType; - private short CalendarType; - private int FirstDateTime; - private int Period; - private int SlidingFlag; - private int PatternSpecific; - private int PatternSpecificNth; - private int EndType; - private int OccurrenceCount; - private int FirstDOW; - private int DeletedInstanceCount; - private Calendar[] DeletedInstanceDates = null; - private int ModifiedInstanceCount; - private Calendar[] ModifiedInstanceDates = null; - private int StartDate; - private int EndDate; - //private int readerVersion2; - private int writerVersion2; - private int StartTimeOffset; - private int EndTimeOffset; - private short ExceptionCount; - private PSTAppointmentException[] Exceptions = null; - private PSTTimeZone RecurrenceTimeZone = null; -} diff --git a/com/pff/PSTAttachment.java b/com/pff/PSTAttachment.java deleted file mode 100644 index 2b2f1db..0000000 --- a/com/pff/PSTAttachment.java +++ /dev/null @@ -1,255 +0,0 @@ -/** - * 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.*; - - -/** - * Class containing attachment information - * @author Richard Johnson - */ -public class PSTAttachment extends PSTObject { - - PSTAttachment(PSTFile theFile, PSTTableBC table, HashMap localDescriptorItems) { - super(theFile, null, table, localDescriptorItems); - } - - public int getSize() { - return this.getIntItem(0x0e20); - } - - public Date getCreationTime() { - return this.getDateItem(0x3007); - } - - public Date getModificationTime() { - return this.getDateItem(0x3008); - } - - public PSTMessage getEmbeddedPSTMessage() - throws IOException, PSTException - { - PSTNodeInputStream in = null; - if ( getIntItem(0x3705) == PSTAttachment.ATTACHMENT_METHOD_EMBEDDED ) { - PSTTableBCItem item = items.get(0x3701); - if ( item.entryValueType == 0x0102 ) { - if ( !item.isExternalValueReference ) - { - in = new PSTNodeInputStream(this.pstFile, item.data); - } else { - // We are in trouble! - throw new PSTException("External reference in getEmbeddedPSTMessage()!\n"); - } - } else if ( item.entryValueType == 0x000D ) { - int descriptorItem = (int)PSTObject.convertLittleEndianBytesToLong(item.data, 0, 4); - //PSTObject.printHexFormatted(item.data, true); - PSTDescriptorItem descriptorItemNested = this.localDescriptorItems.get(descriptorItem); - in = new PSTNodeInputStream(this.pstFile, descriptorItemNested); - this.localDescriptorItems.putAll(pstFile.getPSTDescriptorItems(descriptorItemNested.subNodeOffsetIndexIdentifier)); - /* - if ( descriptorItemNested != null ) { - try { - data = descriptorItemNested.getData(); - blockOffsets = descriptorItemNested.getBlockOffsets(); - } catch (Exception e) { - e.printStackTrace(); - - data = null; - blockOffsets = null; - } - } - * - */ - } - - if ( in == null ) { - return null; - } - - try { - PSTTableBC attachmentTable = new PSTTableBC(in); - return PSTObject.createAppropriatePSTMessageObject(pstFile, this.descriptorIndexNode, attachmentTable, localDescriptorItems); - } catch ( PSTException e ) { - e.printStackTrace(); - } - return null; - } - return null; - } - - public InputStream getFileInputStream() - throws IOException, PSTException - { - - PSTTableBCItem attachmentDataObject = items.get(0x3701); - - if (attachmentDataObject.isExternalValueReference) { - PSTDescriptorItem descriptorItemNested = this.localDescriptorItems.get(attachmentDataObject.entryValueReference); - return new PSTNodeInputStream(this.pstFile, descriptorItemNested); - } else { - // internal value references are never encrypted - return new PSTNodeInputStream(this.pstFile, attachmentDataObject.data, false); - } - - } - - public int getFilesize() - throws PSTException, IOException - { - PSTTableBCItem attachmentDataObject = items.get(0x3701); - if (attachmentDataObject.isExternalValueReference) { - PSTDescriptorItem descriptorItemNested = this.localDescriptorItems.get(attachmentDataObject.entryValueReference); - if (descriptorItemNested == null) { - throw new PSTException("missing attachment descriptor item for: "+attachmentDataObject.entryValueReference); - } - return descriptorItemNested.getDataSize(); - } else { - // raw attachment data, right there! - return attachmentDataObject.data.length; - } - - } - - - // attachment properties - - /** - * Attachment (short) filename ASCII or Unicode string - */ - public String getFilename() { - return this.getStringItem(0x3704); - } - - public static final int ATTACHMENT_METHOD_NONE = 0; - public static final int ATTACHMENT_METHOD_BY_VALUE = 1; - public static final int ATTACHMENT_METHOD_BY_REFERENCE = 2; - public static final int ATTACHMENT_METHOD_BY_REFERENCE_RESOLVE = 3; - public static final int ATTACHMENT_METHOD_BY_REFERENCE_ONLY = 4; - public static final int ATTACHMENT_METHOD_EMBEDDED = 5; - public static final int ATTACHMENT_METHOD_OLE = 6; - - /** - * Attachment method Integer 32-bit signed 0 => None (No attachment) 1 => By value 2 => By reference 3 => By reference resolve 4 => By reference only 5 => Embedded message 6 => OLE - */ - public int getAttachMethod() { - return this.getIntItem(0x3705); - } - /** - * Attachment size - */ - public int getAttachSize() { - return this.getIntItem(0x0e20); - } - /** - * Attachment number - */ - public int getAttachNum() { - return this.getIntItem(0x0e21); - } - /** - * Attachment long filename ASCII or Unicode string - */ - public String getLongFilename() { - return this.getStringItem(0x3707); - } - /** - * Attachment (short) pathname ASCII or Unicode string - */ - public String getPathname() { - return this.getStringItem(0x3708); - } - /** - * Attachment Position Integer 32-bit signed - */ - public int getRenderingPosition() { - return this.getIntItem(0x370b); - } - /** - * Attachment long pathname ASCII or Unicode string - */ - public String getLongPathname() { - return this.getStringItem(0x370d); - } - /** - * Attachment mime type ASCII or Unicode string - */ - public String getMimeTag() { - return this.getStringItem(0x370e); - } - /** - * Attachment mime sequence - */ - public int getMimeSequence() { - return this.getIntItem(0x3710); - } - - /** - * Attachment Content ID - */ - public String getContentId() { - return this.getStringItem(0x3712); - } - - /** - * Attachment not available in HTML - */ - public boolean isAttachmentInvisibleInHtml() { - int actionFlag = this.getIntItem(0x3714); - return ((actionFlag & 0x1) > 0); - } - /** - * Attachment not available in RTF - */ - public boolean isAttachmentInvisibleInRTF() { - int actionFlag = this.getIntItem(0x3714); - return ((actionFlag & 0x2) > 0); - } - /** - * Attachment is MHTML REF - */ - public boolean isAttachmentMhtmlRef() { - int actionFlag = this.getIntItem(0x3714); - return ((actionFlag & 0x4) > 0); - } - - /** - * Attachment content disposition - */ - public String getAttachmentContentDisposition() { - return this.getStringItem(0x3716); - } - -} diff --git a/com/pff/PSTContact.java b/com/pff/PSTContact.java deleted file mode 100644 index 7a1bd10..0000000 --- a/com/pff/PSTContact.java +++ /dev/null @@ -1,864 +0,0 @@ -/** - * 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.IOException; -import java.util.Date; -import java.util.HashMap; - - -/** - * Class for Contacts - * @author Richard Johnson - */ -public class PSTContact extends PSTMessage { - - /** - * @param theFile - * @param descriptorIndexNode - * @throws PSTException - * @throws IOException - */ - public PSTContact(PSTFile theFile, DescriptorIndexNode descriptorIndexNode) - throws PSTException, IOException { - super(theFile, descriptorIndexNode); - } - - /** - * @param theFile - * @param folderIndexNode - * @param table - * @param localDescriptorItems - */ - public PSTContact(PSTFile theFile, DescriptorIndexNode folderIndexNode, PSTTableBC table, HashMap localDescriptorItems) { - super(theFile, folderIndexNode, table, localDescriptorItems); - } - - - /** - * Contact's Account name - */ - public String getAccount() { - return this.getStringItem(0x3a00); - } - /** - * Callback telephone number - */ - public String getCallbackTelephoneNumber() { - return this.getStringItem(0x3a02); - } - /** - * Contact's generational abbreviation FTK: Name suffix - */ - public String getGeneration() { - return this.getStringItem(0x3a05); - } - /** - * Contacts given name - */ - public String getGivenName() { - return this.getStringItem(0x3a06); - } - /** - * Contacts Government ID Number - */ - public String getGovernmentIdNumber() { - return this.getStringItem(0x3a07); - } - - /** - * Business/Office Telephone Number - */ - public String getBusinessTelephoneNumber() { - return this.getStringItem(0x3a08); - } - /** - * Home Telephone Number - */ - public String getHomeTelephoneNumber() { - return this.getStringItem(0x3a09); - } - /** - * Contacts initials - */ - public String getInitials() { - return this.getStringItem(0x3a0a); - } - /** - * Keyword - */ - public String getKeyword() { - return this.getStringItem(0x3a0b); - } - /** - * Contact's language - */ - public String getLanguage() { - return this.getStringItem(0x3a0c); - } - /** - * Contact's location - */ - public String getLocation() { - return this.getStringItem(0x3a0d); - } - /** - * MHS Common Name - */ - public String getMhsCommonName() { - return this.getStringItem(0x3a0f); - } - /** - * Organizational identification number - */ - public String getOrganizationalIdNumber() { - return this.getStringItem(0x3a10); - } - /** - * Contact's surname FTK: Last name - */ - public String getSurname() { - return this.getStringItem(0x3a11); - } - /** - * Original display name - */ - public String getOriginalDisplayName() { - return this.getStringItem(0x3a13); - } - /** - * Default Postal Address - */ - public String getPostalAddress() { - return this.getStringItem(0x3a15); - } - /** - * Contact's company name - */ - public String getCompanyName() { - return this.getStringItem(0x3a16); - } - /** - * Contact's job title FTK: Profession - */ - public String getTitle() { - return this.getStringItem(0x3a17); - } - /** - * Contact's department name Used in contact item - */ - public String getDepartmentName() { - return this.getStringItem(0x3a18); - } - /** - * Contact's office location - */ - public String getOfficeLocation() { - return this.getStringItem(0x3a19); - } - /** - * Primary Telephone - */ - public String getPrimaryTelephoneNumber() { - return this.getStringItem(0x3a1a); - } - - /** - * Contact's secondary office (business) phone number - */ - public String getBusiness2TelephoneNumber() { - return this.getStringItem(0x3a1b); - } - - /** - * Mobile Phone Number - */ - public String getMobileTelephoneNumber() { - return this.getStringItem(0x3a1c); - } - /** - * Radio Phone Number - */ - public String getRadioTelephoneNumber() { - return this.getStringItem(0x3a1d); - } - /** - * Car Phone Number - */ - public String getCarTelephoneNumber() { - return this.getStringItem(0x3a1e); - } - /** - * Other Phone Number - */ - public String getOtherTelephoneNumber() { - return this.getStringItem(0x3a1f); - } - /** - * Transmittable display name - */ - public String getTransmittableDisplayName() { - return this.getStringItem(0x3a20); - } - /** - * Pager Phone Number - */ - public String getPagerTelephoneNumber() { - return this.getStringItem(0x3a21); - } - /** - * Primary Fax Number - */ - public String getPrimaryFaxNumber() { - return this.getStringItem(0x3a23); - } - - /** - * Contact's office (business) fax number - */ - public String getBusinessFaxNumber() { - return this.getStringItem(0x3a24); - } - /** - * Contact's home fax number - */ - public String getHomeFaxNumber() { - return this.getStringItem(0x3a25); - } - - /** - * Business Address Country - */ - public String getBusinessAddressCountry() { - return this.getStringItem(0x3a26); - } - /** - * Business Address City - */ - public String getBusinessAddressCity() { - return this.getStringItem(0x3a27); - } - /** - * Business Address State - */ - public String getBusinessAddressStateOrProvince () { - return this.getStringItem(0x3a28); - } - /** - * Business Address Street - */ - public String getBusinessAddressStreet() { - return this.getStringItem(0x3a29); - } - /** - * Business Postal Code - */ - public String getBusinessPostalCode() { - return this.getStringItem(0x3a2a); - } - /** - * Business PO Box - */ - public String getBusinessPoBox() { - return this.getStringItem(0x3a2b); - } - /** - * Telex Number - */ - public String getTelexNumber() { - return this.getStringItem(0x3a2c); - } - /** - * ISDN Number - */ - public String getIsdnNumber() { - return this.getStringItem(0x3a2d); - } - /** - * Assistant Phone Number - */ - public String getAssistantTelephoneNumber() { - return this.getStringItem(0x3a2e); - } - /** - * Home Phone 2 - */ - public String getHome2TelephoneNumber() { - return this.getStringItem(0x3a2f); - } - /** - * Assistant�s Name - */ - public String getAssistant() { - return this.getStringItem(0x3a30); - } - /** - * Hobbies - */ - public String getHobbies() { - return this.getStringItem(0x3a43); - } - /** - * Middle Name - */ - public String getMiddleName() { - return this.getStringItem(0x3a44); - } - /** - * Display Name Prefix (Contact Title) - */ - public String getDisplayNamePrefix() { - return this.getStringItem(0x3a45); - } - /** - * Profession - */ - public String getProfession() { - return this.getStringItem(0x3a46); - } - /** - * Preferred By Name - */ - public String getPreferredByName() { - return this.getStringItem(0x3a47); - } - /** - * Spouse�s Name - */ - public String getSpouseName() { - return this.getStringItem(0x3a48); - } - /** - * Computer Network Name - */ - public String getComputerNetworkName() { - return this.getStringItem(0x3a49); - } - /** - * Customer ID - */ - public String getCustomerId() { - return this.getStringItem(0x3a4a); - } - /** - * TTY/TDD Phone - */ - public String getTtytddPhoneNumber() { - return this.getStringItem(0x3a4b); - } - /** - * Ftp Site - */ - public String getFtpSite() { - return this.getStringItem(0x3a4c); - } - /** - * Manager�s Name - */ - public String getManagerName() { - return this.getStringItem(0x3a4e); - } - /** - * Nickname - */ - public String getNickname() { - return this.getStringItem(0x3a4f); - } - /** - * Personal Home Page - */ - public String getPersonalHomePage() { - return this.getStringItem(0x3a50); - } - /** - * Business Home Page - */ - public String getBusinessHomePage() { - return this.getStringItem(0x3a51); - } - - /** - * Note - */ - public String getNote() { - return this.getStringItem(0x6619); - } - - String getNamedStringItem(int key) { - int id = pstFile.getNameToIdMapItem(key, PSTFile.PSETID_Address); - if ( id != -1 ) { - return getStringItem(id); - } - return ""; - } - - public String getSMTPAddress() - { - return getNamedStringItem(0x00008084); - } - /** - * Company Main Phone - */ - public String getCompanyMainPhoneNumber() { - return getStringItem(0x3a57); - } - /** - * Children's names - */ - public String getChildrensNames() { - return this.getStringItem(0x3a58); - } - /** - * Home Address City - */ - public String getHomeAddressCity() { - return this.getStringItem(0x3a59); - } - /** - * Home Address Country - */ - public String getHomeAddressCountry() { - return this.getStringItem(0x3a5a); - } - /** - * Home Address Postal Code - */ - public String getHomeAddressPostalCode() { - return this.getStringItem(0x3a5b); - } - /** - * Home Address State or Province - */ - public String getHomeAddressStateOrProvince () { - return this.getStringItem(0x3a5c); - } - /** - * Home Address Street - */ - public String getHomeAddressStreet() { - return this.getStringItem(0x3a5d); - } - /** - * Home Address Post Office Box - */ - public String getHomeAddressPostOfficeBox () { - return this.getStringItem(0x3a5e); - } - /** - * Other Address City - */ - public String getOtherAddressCity() { - return this.getStringItem(0x3a5f); - } - /** - * Other Address Country - */ - public String getOtherAddressCountry() { - return this.getStringItem(0x3a60); - } - /** - * Other Address Postal Code - */ - public String getOtherAddressPostalCode() { - return this.getStringItem(0x3a61); - } - /** - * Other Address State - */ - public String getOtherAddressStateOrProvince () { - return this.getStringItem(0x3a62); - } - /** - * Other Address Street - */ - public String getOtherAddressStreet() { - return this.getStringItem(0x3a63); - } - /** - * Other Address Post Office box - */ - public String getOtherAddressPostOfficeBox() { - return this.getStringItem(0x3a64); - } - - /////////////////////////////////////////////////// - // Below are the values from the name to id map... - /////////////////////////////////////////////////// - - /** - * File under FTK: File as - */ - public String getFileUnder() { - return getNamedStringItem(0x00008005); - } - - /** - * Home Address - */ - public String getHomeAddress() { - return getNamedStringItem(0x0000801a); - } - - /** - * Business Address - */ - public String getWorkAddress() { - return getNamedStringItem(0x0000801b); - } - - /** - * Other Address - */ - public String getOtherAddress() { - return getNamedStringItem(0x0000801c); - } - - /** - * Selected Mailing Address - */ - public int getPostalAddressId() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008022, PSTFile.PSETID_Address)); - } - - /** - * Webpage - */ - public String getHtml() { - return getNamedStringItem(0x0000802b); - } - - /** - * Business Address City - */ - public String getWorkAddressStreet() { - return getNamedStringItem(0x00008045); - } - - /** - * Business Address Street - */ - public String getWorkAddressCity() { - return getNamedStringItem(0x00008046); - } - - /** - * Business Address State - */ - public String getWorkAddressState() { - return getNamedStringItem(0x00008047); - } - - /** - * Business Address Postal Code - */ - public String getWorkAddressPostalCode() { - return getNamedStringItem(0x00008048); - } - - /** - * Business Address Country - */ - public String getWorkAddressCountry() { - return getNamedStringItem(0x00008049); - } - - /** - * Business Address Country - */ - public String getWorkAddressPostOfficeBox() { - return getNamedStringItem(0x0000804A); - } - - /** - * IM Address - */ - public String getInstantMessagingAddress() { - return getNamedStringItem(0x00008062); - } - - /** - * E-mail1 Display Name - */ - public String getEmail1DisplayName() { - return getNamedStringItem(0x00008080); - } - - /** - * E-mail1 Address Type - */ - public String getEmail1AddressType() { - return getNamedStringItem(0x00008082); - } - - /** - * E-mail1 Address - */ - public String getEmail1EmailAddress() { - return getNamedStringItem(0x00008083); - } - - /** - * E-mail1 Display Name - */ - public String getEmail1OriginalDisplayName() { - return getNamedStringItem(0x00008084); - } - - /** - * E-mail1 type - */ - public String getEmail1EmailType() { - return getNamedStringItem(0x00008087); - } - - /** - * E-mail2 display name - */ - public String getEmail2DisplayName() { - return getNamedStringItem(0x00008090); - } - - /** - * E-mail2 address type - */ - public String getEmail2AddressType() { - return getNamedStringItem(0x00008092); - } - - /** - * E-mail2 e-mail address - */ - public String getEmail2EmailAddress() { - return getNamedStringItem(0x00008093); - } - - /** - * E-mail2 original display name - */ - public String getEmail2OriginalDisplayName() { - return getNamedStringItem(0x00008094); - } - - /** - * E-mail3 display name - */ - public String getEmail3DisplayName() { - return getNamedStringItem(0x000080a0); - } - - /** - * E-mail3 address type - */ - public String getEmail3AddressType() { - return getNamedStringItem(0x000080a2); - } - - /** - * E-mail3 e-mail address - */ - public String getEmail3EmailAddress() { - return getNamedStringItem(0x000080a3); - } - - /** - * E-mail3 original display name - */ - public String getEmail3OriginalDisplayName() { - return getNamedStringItem(0x000080a4); - } - - /** - * Fax1 Address Type - */ - public String getFax1AddressType() { - return getNamedStringItem(0x000080b2); - } - - /** - * Fax1 Email Address - */ - public String getFax1EmailAddress() { - return getNamedStringItem(0x000080b3); - } - - /** - * Fax1 Original Display Name - */ - public String getFax1OriginalDisplayName() { - return getNamedStringItem(0x000080b4); - } - - /** - * Fax2 Address Type - */ - public String getFax2AddressType() { - return getNamedStringItem(0x000080c2); - } - - /** - * Fax2 Email Address - */ - public String getFax2EmailAddress() { - return getNamedStringItem(0x000080c3); - } - - /** - * Fax2 Original Display Name - */ - public String getFax2OriginalDisplayName() { - return getNamedStringItem(0x000080c4); - } - - /** - * Fax3 Address Type - */ - public String getFax3AddressType() { - return getNamedStringItem(0x000080d2); - } - - /** - * Fax3 Email Address - */ - public String getFax3EmailAddress() { - return getNamedStringItem(0x000080d3); - } - - /** - * Fax3 Original Display Name - */ - public String getFax3OriginalDisplayName() { - return getNamedStringItem(0x000080d4); - } - - /** - * Free/Busy Location (URL) - */ - public String getFreeBusyLocation() { - return getNamedStringItem(0x000080d8); - } - - /** - * Birthday - */ - public Date getBirthday() { - return this.getDateItem(0x3a42); - } - - /** - * (Wedding) Anniversary - */ - public Date getAnniversary() { - return this.getDateItem(0x3a41); - } - - public String toString() { - - return - "Contact's Account name: "+getAccount()+"\n"+ - "Display Name: "+getGivenName()+" "+getSurname()+" ("+getSMTPAddress()+")\n"+ - "Email1 Address Type: "+getEmail1AddressType()+"\n"+ - "Email1 Address: "+getEmail1EmailAddress()+"\n"+ - "Callback telephone number: "+getCallbackTelephoneNumber()+"\n"+ - "Contact's generational abbreviation (name suffix): "+getGeneration()+"\n"+ - "Contacts given name: "+getGivenName()+"\n"+ - "Contacts Government ID Number: "+getGovernmentIdNumber()+"\n"+ - "Business/Office Telephone Number: "+getBusinessTelephoneNumber()+"\n"+ - "Home Telephone Number: "+getHomeTelephoneNumber()+"\n"+ - "Contacts initials: "+getInitials()+"\n"+ - "Keyword: "+getKeyword()+"\n"+ - "Contact's language: "+getLanguage()+"\n"+ - "Contact's location: "+getLocation()+"\n"+ - "MHS Common Name: "+getMhsCommonName()+"\n"+ - "Organizational identification number: "+getOrganizationalIdNumber()+"\n"+ - "Contact's surname (Last name): "+getSurname()+"\n"+ - "Original display name: "+getOriginalDisplayName()+"\n"+ - "Default Postal Address: "+getPostalAddress()+"\n"+ - "Contact's company name: "+getCompanyName()+"\n"+ - "Contact's job title (Profession): "+getTitle()+"\n"+ - "Contact's department name Used in contact ite: "+getDepartmentName()+"\n"+ - "Contact's office location: "+getOfficeLocation()+"\n"+ - "Primary Telephone: "+getPrimaryTelephoneNumber()+"\n"+ - "Contact's secondary office (business) phone number: "+getBusiness2TelephoneNumber()+"\n"+ - "Mobile Phone Number: "+getMobileTelephoneNumber()+"\n"+ - "Radio Phone Number: "+getRadioTelephoneNumber()+"\n"+ - "Car Phone Number: "+getCarTelephoneNumber()+"\n"+ - "Other Phone Number: "+getOtherTelephoneNumber()+"\n"+ - "Transmittable display name: "+getTransmittableDisplayName()+"\n"+ - "Pager Phone Number: "+getPagerTelephoneNumber()+"\n"+ - "Primary Fax Number: "+getPrimaryFaxNumber()+"\n"+ - "Contact's office (business) fax numbe: "+getBusinessFaxNumber()+"\n"+ - "Contact's home fax number: "+getHomeFaxNumber()+"\n"+ - "Business Address Country: "+getBusinessAddressCountry()+"\n"+ - "Business Address City: "+getBusinessAddressCity()+"\n"+ - "Business Address State: "+getBusinessAddressStateOrProvince ()+"\n"+ - "Business Address Street: "+getBusinessAddressStreet()+"\n"+ - "Business Postal Code: "+getBusinessPostalCode()+"\n"+ - "Business PO Box: "+getBusinessPoBox()+"\n"+ - "Telex Number: "+getTelexNumber()+"\n"+ - "ISDN Number: "+getIsdnNumber()+"\n"+ - "Assistant Phone Number: "+getAssistantTelephoneNumber()+"\n"+ - "Home Phone 2: "+getHome2TelephoneNumber()+"\n"+ - "Assistant's Name: "+getAssistant()+"\n"+ - "Hobbies: "+getHobbies()+"\n"+ - "Middle Name: "+getMiddleName()+"\n"+ - "Display Name Prefix (Contact Title): "+getDisplayNamePrefix()+"\n"+ - "Profession: "+getProfession()+"\n"+ - "Preferred By Name: "+getPreferredByName()+"\n"+ - "Spouse�s Name: "+getSpouseName()+"\n"+ - "Computer Network Name: "+getComputerNetworkName()+"\n"+ - "Customer ID: "+getCustomerId()+"\n"+ - "TTY/TDD Phone: "+getTtytddPhoneNumber()+"\n"+ - "Ftp Site: "+getFtpSite()+"\n"+ - "Manager's Name: "+getManagerName()+"\n"+ - "Nickname: "+getNickname()+"\n"+ - "Personal Home Page: "+getPersonalHomePage()+"\n"+ - "Business Home Page: "+getBusinessHomePage()+"\n"+ - "Company Main Phone: "+getCompanyMainPhoneNumber()+"\n"+ - "Childrens names: "+getChildrensNames()+"\n"+ - "Home Address City: "+getHomeAddressCity()+"\n"+ - "Home Address Country: "+getHomeAddressCountry()+"\n"+ - "Home Address Postal Code: "+getHomeAddressPostalCode()+"\n"+ - "Home Address State or Province: "+getHomeAddressStateOrProvince ()+"\n"+ - "Home Address Street: "+getHomeAddressStreet()+"\n"+ - "Home Address Post Office Box: "+getHomeAddressPostOfficeBox ()+"\n"+ - "Other Address City: "+getOtherAddressCity()+"\n"+ - "Other Address Country: "+getOtherAddressCountry()+"\n"+ - "Other Address Postal Code: "+getOtherAddressPostalCode()+"\n"+ - "Other Address State: "+getOtherAddressStateOrProvince ()+"\n"+ - "Other Address Street: "+getOtherAddressStreet()+"\n"+ - "Other Address Post Office box: "+getOtherAddressPostOfficeBox()+"\n" + - "\n"+ - this.getBody(); - } -} diff --git a/com/pff/PSTDescriptorItem.java b/com/pff/PSTDescriptorItem.java deleted file mode 100644 index 3d67dc7..0000000 --- a/com/pff/PSTDescriptorItem.java +++ /dev/null @@ -1,122 +0,0 @@ -/** - * 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.IOException; -import java.util.*; - -/** - * The descriptor items contain information that describes a PST object. - * This is like extended table entries, usually when the data cannot fit in a traditional table item. - * @author Richard Johnson - */ -class PSTDescriptorItem -{ - PSTDescriptorItem(byte[] data, int offset, PSTFile pstFile) - { - this.pstFile = pstFile; - - if (pstFile.getPSTFileType() == PSTFile.PST_TYPE_ANSI) { - descriptorIdentifier = (int)PSTObject.convertLittleEndianBytesToLong(data, offset, offset+4); - offsetIndexIdentifier = ((int)PSTObject.convertLittleEndianBytesToLong(data, offset+4, offset+8)) - & 0xfffffffe; - subNodeOffsetIndexIdentifier = (int)PSTObject.convertLittleEndianBytesToLong(data, offset+8, offset+12) - & 0xfffffffe; - } else { - descriptorIdentifier = (int)PSTObject.convertLittleEndianBytesToLong(data, offset, offset+4); - offsetIndexIdentifier = ((int)PSTObject.convertLittleEndianBytesToLong(data, offset+8, offset+16)) - & 0xfffffffe; - subNodeOffsetIndexIdentifier = (int)PSTObject.convertLittleEndianBytesToLong(data, offset+16, offset+24) - & 0xfffffffe; - } - } - - public byte[] getData() - throws IOException, PSTException - { - if ( dataBlockData != null ) { - return dataBlockData; - } - - PSTNodeInputStream in = pstFile.readLeaf(offsetIndexIdentifier); - byte[] out = new byte[(int)in.length()]; - in.read(out); - dataBlockData = out; - return dataBlockData; - } - - public int[] getBlockOffsets() - throws IOException, PSTException - { - if ( dataBlockOffsets != null ) { - - return dataBlockOffsets; - } - Long[] offsets = pstFile.readLeaf(offsetIndexIdentifier).getBlockOffsets(); - int[] offsetsOut = new int[offsets.length]; - for (int x = 0; x < offsets.length; x++) { - offsetsOut[x] = offsets[x].intValue(); - } - return offsetsOut; - } - - public int getDataSize() - throws IOException, PSTException - { - return pstFile.getLeafSize(offsetIndexIdentifier); - } - - // Public data - int descriptorIdentifier; - int offsetIndexIdentifier; - int subNodeOffsetIndexIdentifier; - - // These are private to ensure that getData()/getBlockOffets() are used - //private PSTFile.PSTFileBlock dataBlock = null; - byte[] dataBlockData = null; - int[] dataBlockOffsets = null; - private PSTFile pstFile; - - @Override - public String toString() { - return - "PSTDescriptorItem\n"+ - " descriptorIdentifier: "+descriptorIdentifier+"\n"+ - " offsetIndexIdentifier: "+offsetIndexIdentifier+"\n"+ - " subNodeOffsetIndexIdentifier: "+subNodeOffsetIndexIdentifier+"\n"; - - - } - -} diff --git a/com/pff/PSTException.java b/com/pff/PSTException.java deleted file mode 100644 index 36d570e..0000000 --- a/com/pff/PSTException.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * 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; - -/** - * Simple exception for PST File related errors - * @author Richard Johnson - */ -public class PSTException extends Exception -{ - /** - * eclipse generated serial UID - */ - private static final long serialVersionUID = 4284698344354718143L; - - PSTException(String error) { - super(error); - } - PSTException(String error, Exception orig) { - super(error, orig); - } -} diff --git a/com/pff/PSTFile.java b/com/pff/PSTFile.java deleted file mode 100644 index 64e396e..0000000 --- a/com/pff/PSTFile.java +++ /dev/null @@ -1,912 +0,0 @@ -/** - * 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.*; - -/** - * PSTFile is the containing class that allows you to access items within a .pst file. - * Start here, get the root of the folders and work your way down through your items. - * @author Richard Johnson - */ -public class PSTFile { - - public static final int ENCRYPTION_TYPE_NONE = 0; - public static final int ENCRYPTION_TYPE_COMPRESSIBLE = 1; - - private static final int MESSAGE_STORE_DESCRIPTOR_IDENTIFIER = 33; - private static final int ROOT_FOLDER_DESCRIPTOR_IDENTIFIER = 290; - - public static final int PST_TYPE_ANSI = 14; - protected static final int PST_TYPE_ANSI_2 = 15; - public static final int PST_TYPE_UNICODE = 23; - - // Known GUIDs - // Local IDs first - public static final int PS_PUBLIC_STRINGS = 0; - public static final int PSETID_Common = 1; - public static final int PSETID_Address = 2; - public static final int PS_INTERNET_HEADERS = 3; - public static final int PSETID_Appointment = 4; - public static final int PSETID_Meeting = 5; - public static final int PSETID_Log = 6; - public static final int PSETID_Messaging = 7; - public static final int PSETID_Note = 8; - public static final int PSETID_PostRss = 9; - public static final int PSETID_Task = 10; - public static final int PSETID_UnifiedMessaging = 11; - public static final int PS_MAPI = 12; - public static final int PSETID_AirSync = 13; - public static final int PSETID_Sharing = 14; - - // Now the string guids - private static final String guidStrings[] = - { "00020329-0000-0000-C000-000000000046", - "00062008-0000-0000-C000-000000000046", - "00062004-0000-0000-C000-000000000046", - "00020386-0000-0000-C000-000000000046", - "00062002-0000-0000-C000-000000000046", - "6ED8DA90-450B-101B-98DA-00AA003F1305", - "0006200A-0000-0000-C000-000000000046", - "41F28F13-83F4-4114-A584-EEDB5A6B0BFF", - "0006200E-0000-0000-C000-000000000046", - "00062041-0000-0000-C000-000000000046", - "00062003-0000-0000-C000-000000000046", - "4442858E-A9E3-4E80-B900-317A210CC15B", - "00020328-0000-0000-C000-000000000046", - "71035549-0739-4DCB-9163-00F0580DBBDF", - "00062040-0000-0000-C000-000000000046" }; - - private HashMap guidMap = new HashMap(); - - // the type of encryption the files uses. - private int encryptionType = 0; - - // our all important tree. - private LinkedHashMap> childrenDescriptorTree = null; - - private HashMap nameToId = new HashMap(); - private HashMap stringToId = new HashMap(); - private static HashMap idToName = new HashMap(); - private HashMap idToString = new HashMap(); - private byte[] guids = null; - - private int itemCount = 0; - - private RandomAccessFile in; - - /** - * constructor - * @param fileName - * @throws FileNotFoundException - * @throws PSTException - * @throws IOException - */ - public PSTFile(String fileName) - throws FileNotFoundException, PSTException, IOException - { - this(new File(fileName)); - } - public PSTFile(File fileName) - throws FileNotFoundException, PSTException, IOException - { - // attempt to open the file. - in = new RandomAccessFile(fileName, "r"); - - // get the first 4 bytes, should be !BDN - try { - byte[] temp = new byte[4]; - in.read(temp); - String strValue = new String(temp); - if (!strValue.equals("!BDN")) { - throw new PSTException("Invalid file header: "+strValue+", expected: !BDN"); - } - - // make sure we are using a supported version of a PST... - byte[] fileTypeBytes = new byte[2]; - in.seek(10); - in.read(fileTypeBytes); - // ANSI file types can be 14 or 15: - if (fileTypeBytes[0] == PSTFile.PST_TYPE_ANSI_2) { - fileTypeBytes[0] = PSTFile.PST_TYPE_ANSI; - } - if (fileTypeBytes[0] != PSTFile.PST_TYPE_ANSI && - fileTypeBytes[0] != PSTFile.PST_TYPE_UNICODE) - { - throw new PSTException("Unrecognised PST File version: "+fileTypeBytes[0]); - } - this.pstFileType = fileTypeBytes[0]; - - // make sure encryption is turned off at this stage... - if (this.getPSTFileType() == PST_TYPE_ANSI) { - in.seek(461); - } else { - in.seek(513); - } - encryptionType = in.readByte(); - if (encryptionType == 0x02) { - throw new PSTException("Only unencrypted and compressable PST files are supported at this time"); - } - - // build out name to id map. - processNameToIdMap(in); - - } catch (IOException err) { - throw new PSTException("Unable to read PST Sig", err); - } - - } - - private int pstFileType = 0; - public int getPSTFileType() { - return pstFileType; - } - - /** - * read the name-to-id map from the file and load it in - * @param in - * @throws IOException - * @throws PSTException - */ - private void processNameToIdMap(RandomAccessFile in) - throws IOException, PSTException - { - - // Create our guid map - for ( int i = 0; i < guidStrings.length; ++i ) { - UUID uuid = UUID.fromString(guidStrings[i]); - guidMap.put(uuid, i); -/* - System.out.printf("guidMap[{%s}] = %d\n", uuid.toString(), i); -/**/ - } - - // process the name to id map - DescriptorIndexNode nameToIdMapDescriptorNode = (getDescriptorIndexNode(97)); - //nameToIdMapDescriptorNode.readData(this); - - // get the descriptors if we have them - HashMap localDescriptorItems = null; - if (nameToIdMapDescriptorNode.localDescriptorsOffsetIndexIdentifier != 0) { - //PSTDescriptor descriptor = new PSTDescriptor(this, nameToIdMapDescriptorNode.localDescriptorsOffsetIndexIdentifier); - //localDescriptorItems = descriptor.getChildren(); - localDescriptorItems = this.getPSTDescriptorItems(nameToIdMapDescriptorNode.localDescriptorsOffsetIndexIdentifier); - } - - // process the map - //PSTTableBC bcTable = new PSTTableBC(nameToIdMapDescriptorNode.dataBlock.data, nameToIdMapDescriptorNode.dataBlock.blockOffsets); - OffsetIndexItem off = this.getOffsetIndexNode(nameToIdMapDescriptorNode.dataOffsetIndexIdentifier); - PSTNodeInputStream nodein = new PSTNodeInputStream(this, off); - byte[] tmp = new byte[1024]; - nodein.read(tmp); - PSTTableBC bcTable = new PSTTableBC(nodein); - - HashMap tableItems = (bcTable.getItems()); - // Get the guids - PSTTableBCItem guidEntry = tableItems.get(2); // PidTagNameidStreamGuid - guids = getData(guidEntry, localDescriptorItems); - int nGuids = guids.length / 16; - UUID[] uuidArray = new UUID[nGuids]; - int[] uuidIndexes = new int[nGuids]; - int offset = 0; - for ( int i = 0; i < nGuids; ++i ) { - long mostSigBits = (PSTObject.convertLittleEndianBytesToLong(guids, offset, offset+4) << 32) | - (PSTObject.convertLittleEndianBytesToLong(guids, offset+4, offset+6) << 16) | - PSTObject.convertLittleEndianBytesToLong(guids, offset+6, offset+8); - long leastSigBits = PSTObject.convertBigEndianBytesToLong(guids, offset+8, offset+16); - uuidArray[i] = new UUID(mostSigBits, leastSigBits); - if ( guidMap.containsKey(uuidArray[i]) ) { - uuidIndexes[i] = guidMap.get(uuidArray[i]); - } else { - uuidIndexes[i] = -1; // We don't know this guid - } -/* - System.out.printf("uuidArray[%d] = {%s},%d\n", i, uuidArray[i].toString(), uuidIndexes[i]); -/**/ - offset += 16; - } - - // if we have a reference to an internal descriptor - PSTTableBCItem mapEntries = tableItems.get(3); // - byte[] nameToIdByte = getData(mapEntries, localDescriptorItems); - - PSTTableBCItem stringMapEntries = tableItems.get(4); // - byte[] stringNameToIdByte = getData(stringMapEntries, localDescriptorItems); - - // process the entries - for (int x = 0; x+8 < nameToIdByte.length; x += 8) { - int dwPropertyId = (int)PSTObject.convertLittleEndianBytesToLong(nameToIdByte, x, x+4); - int wGuid = (int)PSTObject.convertLittleEndianBytesToLong(nameToIdByte, x+4, x+6); - int wPropIdx = ((int)PSTObject.convertLittleEndianBytesToLong(nameToIdByte, x+6, x+8)); - - if ( (wGuid & 0x0001) == 0 ) { - wPropIdx += 0x8000; - wGuid >>= 1; - int guidIndex; - if ( wGuid == 1 ) { - guidIndex = PS_MAPI; - } else if ( wGuid == 2 ) { - guidIndex = PS_PUBLIC_STRINGS; - } else { - guidIndex = uuidIndexes[wGuid-3]; - } - nameToId.put((long)dwPropertyId | ((long)guidIndex << 32), wPropIdx); - idToName.put(wPropIdx, (long)dwPropertyId); -/* - System.out.printf("0x%08X:%04X, 0x%08X\n", dwPropertyId, guidIndex, wPropIdx); -/**/ - } else { - // else the identifier is a string - // dwPropertyId becomes thHke byte offset into the String stream in which the string name of the property is stored. - int len = (int)PSTObject.convertLittleEndianBytesToLong( - stringNameToIdByte, - dwPropertyId, - dwPropertyId+4 - ); - byte[] keyByteValue = new byte[len]; - System.arraycopy(stringNameToIdByte, dwPropertyId+4, keyByteValue, 0, keyByteValue.length); - wPropIdx += 0x8000; - String key = new String(keyByteValue, "UTF-16LE"); - stringToId.put(key, wPropIdx); - idToString.put(wPropIdx, key); - /* - if (wPropIdx == 32784) { - System.out.println("here!" + dwPropertyId); - System.out.println(key); - //System.out.println(32784 - 0x8000); - } - */ - } - } - } - - - private byte [] getData(PSTTableItem item, HashMap localDescriptorItems) - throws IOException, PSTException - { - if ( item.data.length != 0 ) { - return item.data; - } - - if ( localDescriptorItems == null ) { - throw new PSTException("External reference but no localDescriptorItems in PSTFile.getData()"); - } - - if ( item.entryValueType != 0x0102 ) { - throw new PSTException("Attempting to get non-binary data in PSTFile.getData()"); - } - - PSTDescriptorItem mapDescriptorItem = localDescriptorItems.get(item.entryValueReference); - if (mapDescriptorItem == null) { - throw new PSTException ("not here "+item.entryValueReference + "\n"+localDescriptorItems.keySet()); - } - return mapDescriptorItem.getData(); - } - - int getNameToIdMapItem(int key, int propertySetIndex) - { - long lKey = ((long)propertySetIndex << 32) | (long)key; - Integer i = nameToId.get(lKey); - if ( i == null ) - { - return -1; - } - return i; - } - int getPublicStringToIdMapItem(String key) - { - Integer i = this.stringToId.get(key); - if (i == null) { - return -1; - } - return i; - } - - - static long getNameToIdMapKey(int id) - //throws PSTException - { - Long i = idToName.get(id); - if ( i == null ) - { - //throw new PSTException("Name to Id mapping not found"); - return -1; - } - return i; - } - - - - static private Properties propertyInternetCodePages = null; - static private boolean bCPFirstTime = true; - static String getInternetCodePageCharset(int propertyId) { - if ( bCPFirstTime ) { - bCPFirstTime = false; - propertyInternetCodePages = new Properties(); - try { - InputStream propertyStream = PSTFile.class.getResourceAsStream("/InternetCodepages.txt"); - if ( propertyStream != null ) { - propertyInternetCodePages.load(propertyStream); - } else { - propertyInternetCodePages = null; - } - } catch (FileNotFoundException e) { - propertyInternetCodePages = null; - e.printStackTrace(); - } catch (IOException e) { - propertyInternetCodePages = null; - e.printStackTrace(); - } - } - if ( propertyInternetCodePages != null ) { - return propertyInternetCodePages.getProperty(propertyId+""); - } - return null; - } - - - static private Properties propertyNames = null; - static private boolean bFirstTime = true; - - static String getPropertyName(int propertyId, boolean bNamed) { - if ( bFirstTime ) { - bFirstTime = false; - propertyNames = new Properties(); - try { - InputStream propertyStream = PSTFile.class.getResourceAsStream("/PropertyNames.txt"); - if ( propertyStream != null ) { - propertyNames.load(propertyStream); - } else { - propertyNames = null; - } - } catch (FileNotFoundException e) { - propertyNames = null; - e.printStackTrace(); - } catch (IOException e) { - propertyNames = null; - e.printStackTrace(); - } - } - - if ( propertyNames != null ) { - String key = String.format((bNamed ? "%08X" : "%04X"), propertyId); - return propertyNames.getProperty(key); - } - - return null; - } - - static String getPropertyDescription(int entryType, int entryValueType) { - String ret = ""; - if ( entryType < 0x8000 ) { - String name = PSTFile.getPropertyName(entryType, false); - if ( name != null ) { - ret = String.format("%s:%04X: ", name, entryValueType); - } else { - ret = String.format("0x%04X:%04X: ", entryType, entryValueType); - } - } else { - long type = PSTFile.getNameToIdMapKey(entryType); - if ( type == -1 ) { - ret = String.format("0xFFFF(%04X):%04X: ", entryType, entryValueType); - } else { - String name = PSTFile.getPropertyName((int)type, true); - if ( name != null ) { - ret = String.format("%s(%04X):%04X: ", name, entryType, entryValueType); - } else { - ret = String.format("0x%04X(%04X):%04X: ", type, entryType, entryValueType); - } - } - } - - return ret; - } - - /** - * destructor just closes the file handle... - */ - @Override - protected void finalize() - throws IOException - { - in.close(); - } - - /** - * get the type of encryption the file uses - * @return encryption type used in the PST File - */ - public int getEncryptionType() { - return this.encryptionType; - } - - /** - * get the handle to the file we are currently accessing - */ - public RandomAccessFile getFileHandle() { - return this.in; - } - - - /** - * get the message store of the PST file. - * Note that this doesn't really have much information, better to look under the root folder - * @throws PSTException - * @throws IOException - */ - public PSTMessageStore getMessageStore() - throws PSTException, IOException - { - DescriptorIndexNode messageStoreDescriptor = getDescriptorIndexNode(MESSAGE_STORE_DESCRIPTOR_IDENTIFIER); - return new PSTMessageStore(this, messageStoreDescriptor); - } - - /** - * get the root folder for the PST file. - * You should find all of your data under here... - * @throws PSTException - * @throws IOException - */ - public PSTFolder getRootFolder() - throws PSTException, IOException - { - DescriptorIndexNode rootFolderDescriptor = getDescriptorIndexNode(ROOT_FOLDER_DESCRIPTOR_IDENTIFIER); - PSTFolder output = new PSTFolder(this, rootFolderDescriptor); - return output; - } - - - - PSTNodeInputStream readLeaf(long bid) - throws IOException, PSTException - { - //PSTFileBlock ret = null; - PSTNodeInputStream ret = null; - - // get the index node for the descriptor index - OffsetIndexItem offsetItem = getOffsetIndexNode(bid); - return new PSTNodeInputStream(this, offsetItem); - - } - - - public int getLeafSize(long bid) - throws IOException, PSTException - { - OffsetIndexItem offsetItem = getOffsetIndexNode(bid); - - // Internal block? - if ( (offsetItem.indexIdentifier & 0x02) == 0 ) { - // No, return the raw size - return offsetItem.size; - } - - // we only need the first 8 bytes - byte[] data = new byte[8]; - in.seek(offsetItem.fileOffset); - in.read(data); - - // we are an array, get the sum of the sizes... - return (int)PSTObject.convertLittleEndianBytesToLong(data, 4, 8); - } - - /** - * Read a file offset from the file - * PST Files have this tendency to store file offsets (pointers) in 8 little endian bytes. - * Convert this to a long for seeking to. - * @param in handle for PST file - * @param startOffset where to read the 8 bytes from - * @return long representing the read location - * @throws IOException - */ - protected long extractLEFileOffset(long startOffset) - throws IOException - { - long offset = 0; - if (this.getPSTFileType() == PSTFile.PST_TYPE_ANSI) { - in.seek(startOffset); - byte[] temp = new byte[4]; - in.read(temp); - offset |= temp[3] & 0xff; - offset <<= 8; - offset |= temp[2] & 0xff; - offset <<= 8; - offset |= temp[1] & 0xff; - offset <<= 8; - offset |= temp[0] & 0xff; - } else { - in.seek(startOffset); - byte[] temp = new byte[8]; - in.read(temp); - offset = temp[7] & 0xff; - long tmpLongValue; - for (int x = 6; x >= 0; x--) { - offset = offset << 8; - tmpLongValue = (long)temp[x] & 0xff; - offset |= tmpLongValue; - } - } - - return offset; - } - - /** - * Generic function used by getOffsetIndexNode and getDescriptorIndexNode for navigating the PST B-Trees - * @param in - * @param index - * @param descTree - * @return - * @throws IOException - * @throws PSTException - */ - private byte[] findBtreeItem(RandomAccessFile in, long index, boolean descTree) - throws IOException, PSTException - { - - long btreeStartOffset; - // first find the starting point for the offset index - if (this.getPSTFileType() == PST_TYPE_ANSI) { - btreeStartOffset = this.extractLEFileOffset(196); - if (descTree) { - btreeStartOffset = this.extractLEFileOffset(188); - } - } else { - btreeStartOffset = this.extractLEFileOffset(240); - if (descTree) { - btreeStartOffset = this.extractLEFileOffset(224); - } - } - - // okay, what we want to do is navigate the tree until you reach the bottom.... - // try and read the index b-tree - byte[] temp = new byte[2]; - if (this.getPSTFileType() == PST_TYPE_ANSI) { - in.seek(btreeStartOffset+500); - } else { - in.seek(btreeStartOffset+496); - } - in.read(temp); - while ((temp[0] == 0xffffff80 && temp[1] == 0xffffff80 && !descTree) || - (temp[0] == 0xffffff81 && temp[1] == 0xffffff81 && descTree)) - { - - // get the rest of the data.... - byte[] branchNodeItems; - if (this.getPSTFileType() == PST_TYPE_ANSI) { - branchNodeItems = new byte[496]; - } else { - branchNodeItems = new byte[488]; - } - in.seek(btreeStartOffset); - in.read(branchNodeItems); - - int numberOfItems = in.read(); - in.read(); // maxNumberOfItems - in.read(); // itemSize - int levelsToLeaf = in.read(); - - if (levelsToLeaf > 0) { - boolean found = false; - for (int x = 0; x < numberOfItems; x++) { - if (this.getPSTFileType() == PST_TYPE_ANSI) { - long indexIdOfFirstChildNode = extractLEFileOffset(btreeStartOffset + (x * 12)); - if (indexIdOfFirstChildNode > index) { - // get the address for the child first node in this group - btreeStartOffset = extractLEFileOffset(btreeStartOffset+((x-1) * 12)+8); - in.seek(btreeStartOffset+500); - in.read(temp); - found = true; - break; - } - } else { - long indexIdOfFirstChildNode = extractLEFileOffset(btreeStartOffset + (x * 24)); - if (indexIdOfFirstChildNode > index) { - // get the address for the child first node in this group - btreeStartOffset = extractLEFileOffset(btreeStartOffset+((x-1) * 24)+16); - in.seek(btreeStartOffset+496); - in.read(temp); - found = true; - break; - } - } - } - if (!found) { - // it must be in the very last branch... - if (this.getPSTFileType() == PST_TYPE_ANSI) { - btreeStartOffset = extractLEFileOffset(btreeStartOffset+((numberOfItems-1) * 12)+8); - in.seek(btreeStartOffset+500); - in.read(temp); - } else { - btreeStartOffset = extractLEFileOffset(btreeStartOffset+((numberOfItems-1) * 24)+16); - in.seek(btreeStartOffset+496); - in.read(temp); - } - } - } - else - { - // we are at the bottom of the tree... - // we want to get our file offset! - for (int x = 0; x < numberOfItems; x++) { - - if (this.getPSTFileType() == PSTFile.PST_TYPE_ANSI) { - if (descTree) - { - // The 32-bit descriptor index b-tree leaf node item - in.seek(btreeStartOffset + (x * 16)); - temp = new byte[4]; - in.read(temp); - if (PSTObject.convertLittleEndianBytesToLong(temp) == index) { - // give me the offset index please! - in.seek(btreeStartOffset + (x * 16)); - temp = new byte[16]; - in.read(temp); - return temp; - } - } - else - { - // The 32-bit (file) offset index item - long indexIdOfFirstChildNode = extractLEFileOffset(btreeStartOffset + (x * 12)); - - if (indexIdOfFirstChildNode == index) { - // we found it!!!! OMG - //System.out.println("item found as item #"+x); - in.seek(btreeStartOffset + (x * 12)); - - temp = new byte[12]; - in.read(temp); - return temp; - } - } - } else { - if (descTree) - { - // The 64-bit descriptor index b-tree leaf node item - in.seek(btreeStartOffset + (x * 32)); - - temp = new byte[4]; - in.read(temp); - if (PSTObject.convertLittleEndianBytesToLong(temp) == index) { - // give me the offset index please! - in.seek(btreeStartOffset + (x * 32)); - temp = new byte[32]; - in.read(temp); - return temp; - } - } - else - { - // The 64-bit (file) offset index item - long indexIdOfFirstChildNode = extractLEFileOffset(btreeStartOffset + (x * 24)); - - if (indexIdOfFirstChildNode == index) { - // we found it!!!! OMG - //System.out.println("item found as item #"+x); - in.seek(btreeStartOffset + (x * 24)); - - temp = new byte[24]; - in.read(temp); - return temp; - } - } - } - } - throw new PSTException("Unable to find "+index); - } - } - - throw new PSTException("Unable to find node: "+index); - } - - /** - * navigate the internal descriptor B-Tree and find a specific item - * @param in - * @param identifier - * @return the descriptor node for the item - * @throws IOException - * @throws PSTException - */ - DescriptorIndexNode getDescriptorIndexNode(long identifier) - throws IOException, PSTException - { - return new DescriptorIndexNode(findBtreeItem(in, identifier, true), this.getPSTFileType()); - } - - /** - * navigate the internal index B-Tree and find a specific item - * @param in - * @param identifier - * @return the offset index item - * @throws IOException - * @throws PSTException - */ - OffsetIndexItem getOffsetIndexNode(long identifier) - throws IOException, PSTException - { - return new OffsetIndexItem(findBtreeItem(in, identifier, false), this.getPSTFileType()); - } - - - /** - * parse a PSTDescriptor and get all of its items - */ - HashMap getPSTDescriptorItems(long localDescriptorsOffsetIndexIdentifier) - throws PSTException, IOException - { - return this.getPSTDescriptorItems(this.readLeaf(localDescriptorsOffsetIndexIdentifier)); - } - HashMap getPSTDescriptorItems(PSTNodeInputStream in) - throws PSTException, IOException - { - // make sure the signature is correct - in.seek(0); - int sig = in.read(); - if (sig != 0x2) { - throw new PSTException("Unable to process descriptor node, bad signature: "+sig); - } - - HashMap output = new HashMap(); - int numberOfItems = (int)in.seekAndReadLong(2, 2); - int offset; - if (this.getPSTFileType() == PSTFile.PST_TYPE_ANSI) { - offset = 4; - } else { - offset = 8; - } - - byte[] data = new byte[(int)in.length()]; - in.seek(0); - in.read(data); - - for (int x = 0; x < numberOfItems; x++) { - PSTDescriptorItem item = new PSTDescriptorItem(data, offset, this); - output.put(item.descriptorIdentifier, item); - if (this.getPSTFileType() == PSTFile.PST_TYPE_ANSI) { - offset += 12; - } else { - offset += 24; - } - } - - return output; - } - - /** - * Build the children descriptor tree - * This goes through the entire descriptor B-Tree and adds every item to the childrenDescriptorTree. - * This is used as fallback when the nodes that list file contents are broken. - * @param in - * @throws IOException - * @throws PSTException - */ - LinkedHashMap> getChildDescriptorTree() - throws IOException, PSTException - { - if (this.childrenDescriptorTree == null) { - long btreeStartOffset = 0; - if (this.getPSTFileType() == PST_TYPE_ANSI) { - btreeStartOffset = this.extractLEFileOffset(188); - } else { - btreeStartOffset = this.extractLEFileOffset(224); - } - this.childrenDescriptorTree = new LinkedHashMap>(); - processDescriptorBTree(btreeStartOffset); - } - return this.childrenDescriptorTree; - } - - /** - * Recursive function for building the descriptor tree, used by buildDescriptorTree - * @param in - * @param btreeStartOffset - * @throws IOException - * @throws PSTException - */ - private void processDescriptorBTree(long btreeStartOffset) - throws IOException, PSTException - { - byte[] temp = new byte[2]; - if (this.getPSTFileType() == PST_TYPE_ANSI) { - in.seek(btreeStartOffset+500); - } else { - in.seek(btreeStartOffset+496); - } - in.read(temp); - - if ((temp[0] == 0xffffff81 && temp[1] == 0xffffff81)) { - - if (this.getPSTFileType() == PST_TYPE_ANSI) { - in.seek(btreeStartOffset+496); - } else { - in.seek(btreeStartOffset+488); - } - - int numberOfItems = in.read(); - in.read(); // maxNumberOfItems - in.read(); // itemSize - int levelsToLeaf = in.read(); - - if (levelsToLeaf > 0) { - for (int x = 0; x < numberOfItems; x++) { - if (this.getPSTFileType() == PST_TYPE_ANSI) { - long branchNodeItemStartIndex = (btreeStartOffset + (12*x)); - long nextLevelStartsAt = this.extractLEFileOffset(branchNodeItemStartIndex+8); - processDescriptorBTree(nextLevelStartsAt); - } else { - long branchNodeItemStartIndex = (btreeStartOffset + (24*x)); - long nextLevelStartsAt = this.extractLEFileOffset(branchNodeItemStartIndex+16); - processDescriptorBTree(nextLevelStartsAt); - } - } - } else { - for (int x = 0; x < numberOfItems; x++) { - // The 64-bit descriptor index b-tree leaf node item - // give me the offset index please! - if (this.getPSTFileType() == PSTFile.PST_TYPE_ANSI) { - in.seek(btreeStartOffset + (x * 16)); - temp = new byte[16]; - in.read(temp); - } else { - in.seek(btreeStartOffset + (x * 32)); - temp = new byte[32]; - in.read(temp); - } - - DescriptorIndexNode tempNode = new DescriptorIndexNode(temp, this.getPSTFileType()); - - // we don't want to be children of ourselves... - if (tempNode.parentDescriptorIndexIdentifier == tempNode.descriptorIdentifier) { - // skip! - } else if (childrenDescriptorTree.containsKey(tempNode.parentDescriptorIndexIdentifier)) { - // add this entry to the existing list of children - LinkedList children = childrenDescriptorTree.get(tempNode.parentDescriptorIndexIdentifier); - children.add(tempNode); - } else { - // create a new entry and add this one to that - LinkedList children = new LinkedList(); - children.add(tempNode); - childrenDescriptorTree.put(tempNode.parentDescriptorIndexIdentifier, children); - } - this.itemCount++; - } - } - } else { - PSTObject.printHexFormatted(temp, true); - throw new PSTException("Unable to read descriptor node, is not a descriptor"); - } - } - - -} diff --git a/com/pff/PSTFolder.java b/com/pff/PSTFolder.java deleted file mode 100644 index d3fc0a4..0000000 --- a/com/pff/PSTFolder.java +++ /dev/null @@ -1,405 +0,0 @@ -/** - * 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.*; - - -/** - * Represents a folder in the PST File - * Allows you to access child folders or items. Items are accessed through a sort of cursor arrangement. - * This allows for incremental reading of a folder which may have _lots_ of emails. - * @author Richard Johnson - */ -public class PSTFolder extends PSTObject { - - /** - * a constructor for the rest of us... - * @param theFile - * @param descriptorIndexNode - * @throws PSTException - * @throws IOException - */ - PSTFolder(PSTFile theFile, DescriptorIndexNode descriptorIndexNode) - throws PSTException, IOException - { - super(theFile, descriptorIndexNode); - } - - /** - * For pre-populating a folder object with values. - * Not recommended for use outside this library - * @param theFile - * @param folderIndexNode - * @param table - */ - PSTFolder(PSTFile theFile, DescriptorIndexNode folderIndexNode, PSTTableBC table, HashMap localDescriptorItems) { - super(theFile, folderIndexNode, table, localDescriptorItems); - } - - /** - * get all of the sub folders... - * there are not usually thousands, so we just do it in one big operation. - * @return all of the subfolders - * @throws PSTException - * @throws IOException - */ - public Vector getSubFolders() - throws PSTException, IOException - { - initSubfoldersTable(); - Vector output = new Vector(); - if (this.hasSubfolders()) { - try { - List> itemMapSet = subfoldersTable.getItems(); - for (HashMap itemMap : itemMapSet) { - PSTTable7CItem item = itemMap.get(26610); - PSTFolder folder = (PSTFolder)PSTObject.detectAndLoadPSTObject(pstFile, item.entryValueReference); - output.add(folder); - } - } catch (PSTException err) { - // hierachy node doesn't exist - throw new PSTException("Can't get child folders for folder "+this.getDisplayName()+"("+this.getDescriptorNodeId()+") child count: "+this.getContentCount()+ " - "+err.toString()); - } - } - // try and get subfolders? - return output; - } - - private void initSubfoldersTable() - throws IOException, PSTException - { - if (subfoldersTable != null) { - return; - } - - if (this.hasSubfolders()) { - long folderDescriptorIndex = this.descriptorIndexNode.descriptorIdentifier + 11; - try { - DescriptorIndexNode folderDescriptor = this.pstFile.getDescriptorIndexNode(folderDescriptorIndex); - HashMap tmp = null; - if (folderDescriptor.localDescriptorsOffsetIndexIdentifier > 0) { - //tmp = new PSTDescriptor(pstFile, folderDescriptor.localDescriptorsOffsetIndexIdentifier).getChildren(); - tmp = pstFile.getPSTDescriptorItems(folderDescriptor.localDescriptorsOffsetIndexIdentifier); - } - subfoldersTable = new PSTTable7C(new PSTNodeInputStream(pstFile, pstFile.getOffsetIndexNode(folderDescriptor.dataOffsetIndexIdentifier)), tmp); - } catch (PSTException err) { - // hierachy node doesn't exist - throw new PSTException("Can't get child folders for folder "+this.getDisplayName()+"("+this.getDescriptorNodeId()+") child count: "+this.getContentCount()+ " - "+err.toString()); - } - } - - } - - /** - * internal vars for the tracking of things.. - */ - private int currentEmailIndex = 0; - private LinkedHashSet otherItems = null; - - private PSTTable7C emailsTable = null; - private LinkedList fallbackEmailsTable = null; - private PSTTable7C subfoldersTable = null; - - /** - * this method goes through all of the children and sorts them into one of the three hash sets. - * @throws PSTException - * @throws IOException - */ - private void initEmailsTable() - throws PSTException, IOException - { - if (this.emailsTable != null || this.fallbackEmailsTable != null) { - return; - } - - // some folder types don't have children: - if (this.getNodeType() == PSTObject.NID_TYPE_SEARCH_FOLDER) { - return; - } - - try { - long folderDescriptorIndex = this.descriptorIndexNode.descriptorIdentifier + 12; // +12 lists emails! :D - DescriptorIndexNode folderDescriptor = this.pstFile.getDescriptorIndexNode(folderDescriptorIndex); - HashMap tmp = null; - if (folderDescriptor.localDescriptorsOffsetIndexIdentifier > 0) { - //tmp = new PSTDescriptor(pstFile, folderDescriptor.localDescriptorsOffsetIndexIdentifier).getChildren(); - tmp = pstFile.getPSTDescriptorItems(folderDescriptor.localDescriptorsOffsetIndexIdentifier); - } - //PSTTable7CForFolder folderDescriptorTable = new PSTTable7CForFolder(folderDescriptor.dataBlock.data, folderDescriptor.dataBlock.blockOffsets,tmp, 0x67F2); - emailsTable = new PSTTable7C( - new PSTNodeInputStream(pstFile, pstFile.getOffsetIndexNode(folderDescriptor.dataOffsetIndexIdentifier)), - tmp, - 0x67F2 - ); - } catch (Exception err) { - - // here we have to attempt to fallback onto the children as listed by the descriptor b-tree - LinkedHashMap> tree = this.pstFile.getChildDescriptorTree(); - - fallbackEmailsTable = new LinkedList(); - LinkedList allChildren = tree.get(this.getDescriptorNode().descriptorIdentifier); - - if (allChildren != null) { - // quickly go through and remove those entries that are not messages! - for (DescriptorIndexNode node : allChildren) { - if (node != null && PSTObject.getNodeType(node.descriptorIdentifier) == PSTObject.NID_TYPE_NORMAL_MESSAGE) { - fallbackEmailsTable.add(node); - } - } - } - - System.err.println( - "Can't get children for folder "+ - this.getDisplayName()+ - "("+this.getDescriptorNodeId()+") child count: "+ - this.getContentCount()+ " - "+ - err.toString()+ ", using alternate child tree with " + fallbackEmailsTable.size()+" items"); - } - } - - /** - * get some children from the folder - * This is implemented as a cursor of sorts, as there could be thousands - * and that is just too many to process at once. - * @param numberToReturn - * @return bunch of children in this folder - * @throws PSTException - * @throws IOException - */ - public Vector getChildren(int numberToReturn) - throws PSTException, IOException - { - initEmailsTable(); - - Vector output = new Vector(); - if (emailsTable != null) { - List> rows = this.emailsTable.getItems(currentEmailIndex, numberToReturn); - - for (int x = 0; x < rows.size(); x++) { - if (this.currentEmailIndex >= this.getContentCount()) - { - // no more! - break; - } - // get the emails from the rows - PSTTable7CItem emailRow = rows.get(x).get(0x67F2); - DescriptorIndexNode childDescriptor = pstFile.getDescriptorIndexNode(emailRow.entryValueReference); - PSTObject child = PSTObject.detectAndLoadPSTObject(pstFile, childDescriptor); - output.add(child); - currentEmailIndex++; - } - } else if (fallbackEmailsTable != null) { - // we use the fallback - ListIterator iterator = this.fallbackEmailsTable.listIterator(currentEmailIndex); - for (int x = 0; x < numberToReturn; x++) { - if (this.currentEmailIndex >= this.getContentCount()) - { - // no more! - break; - } - DescriptorIndexNode childDescriptor = iterator.next(); - PSTObject child = PSTObject.detectAndLoadPSTObject(pstFile, childDescriptor); - output.add(child); - currentEmailIndex++; - } - } - - - return output; - } - - public LinkedList getChildDescriptorNodes() throws PSTException, IOException { - initEmailsTable(); - if (this.emailsTable == null) { - return new LinkedList(); - } - LinkedList output = new LinkedList(); - List> rows = this.emailsTable.getItems(); - for (HashMap row : rows) { - // get the emails from the rows - if (this.currentEmailIndex == this.getContentCount()) - { - // no more! - break; - } - PSTTable7CItem emailRow = row.get(0x67F2); - if (emailRow.entryValueReference == 0) { - break; - } - output.add(emailRow.entryValueReference); - } - return output; - } - - - - /** - * Get the next child of this folder - * As there could be thousands of emails, we have these kind of cursor operations - * @return the next email in the folder or null if at the end of the folder - * @throws PSTException - * @throws IOException - */ - public PSTObject getNextChild() - throws PSTException, IOException - { - initEmailsTable(); - - if (this.emailsTable != null) { - List> rows = this.emailsTable.getItems(currentEmailIndex, 1); - - if (this.currentEmailIndex == this.getContentCount()) - { - // no more! - return null; - } - // get the emails from the rows - PSTTable7CItem emailRow = rows.get(0).get(0x67F2); - DescriptorIndexNode childDescriptor = pstFile.getDescriptorIndexNode(emailRow.entryValueReference); - PSTObject child = PSTObject.detectAndLoadPSTObject(pstFile, childDescriptor); - currentEmailIndex++; - - return child; - } else if (this.fallbackEmailsTable != null) { - if (this.currentEmailIndex >= this.getContentCount() || this.currentEmailIndex >= this.fallbackEmailsTable.size()) - { - // no more! - return null; - } - // get the emails from the rows - DescriptorIndexNode childDescriptor = fallbackEmailsTable.get(currentEmailIndex); - PSTObject child = PSTObject.detectAndLoadPSTObject(pstFile, childDescriptor); - currentEmailIndex++; - return child; - } - return null; - } - - /** - * move the internal folder cursor to the desired position - * position 0 is before the first record. - * @param newIndex - */ - public void moveChildCursorTo(int newIndex) - throws IOException, PSTException - { - initEmailsTable(); - - if (newIndex < 1) { - currentEmailIndex = 0; - return; - } - if (newIndex > this.getContentCount()) { - newIndex = this.getContentCount(); - } - currentEmailIndex = newIndex; - } - - /** - * the number of child folders in this folder - * @return number of subfolders as counted - * @throws IOException - * @throws PSTException - */ - public int getSubFolderCount() - throws IOException, PSTException - { - this.initSubfoldersTable(); - if (this.subfoldersTable != null) - return this.subfoldersTable.getRowCount(); - else - return 0; - } - - /** - * the number of emails in this folder - * this is the count of emails made by the library and will therefore should be more accurate than getContentCount - * @return number of emails in this folder (as counted) - * @throws IOException - * @throws PSTException - public int getEmailCount() - throws IOException, PSTException - { - this.initEmailsTable(); - return this.emailsTable.getRowCount(); - } - */ - - - public int getFolderType() { - return this.getIntItem(0x3601); - } - - /** - * the number of emails in this folder - * this is as reported by the PST file, for a number calculated by the library use getEmailCount - * @return number of items as reported by PST File - */ - public int getContentCount() { - return this.getIntItem(0x3602); - } - - /** - * Amount of unread content items Integer 32-bit signed - */ - public int getUnreadCount() { - return this.getIntItem(0x3603); - } - - /** - * does this folder have subfolders - * once again, read from the PST, use getSubFolderCount if you want to know what the library makes of it all - * @return has subfolders as reported by the PST File - */ - public boolean hasSubfolders() { - return (this.getIntItem(0x360a) != 0); - } - - public String getContainerClass() { - return this.getStringItem(0x3613); - } - - public int getAssociateContentCount() { - return this.getIntItem(0x3617); - } - - /** - * Container flags Integer 32-bit signed - */ - public int getContainerFlags() { - return this.getIntItem(0x3600); - } - -} diff --git a/com/pff/PSTMessage.java b/com/pff/PSTMessage.java deleted file mode 100644 index 2644276..0000000 --- a/com/pff/PSTMessage.java +++ /dev/null @@ -1,1017 +0,0 @@ -/** - * 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.IOException; -import java.util.Date; -import java.util.HashMap; - -/** - * PST Message contains functions that are common across most MAPI objects. - * Note that many of these functions may not be applicable for the item in question, - * however there seems to be no hard and fast outline for what properties apply to which - * objects. For properties where no value is set, a blank value is returned (rather than - * an exception being raised). - * @author Richard Johnson - */ -public class PSTMessage extends PSTObject { - - public static final int IMPORTANCE_LOW = 0; - public static final int IMPORTANCE_NORMAL = 1; - public static final int IMPORTANCE_HIGH = 2; - - PSTMessage(PSTFile theFile, DescriptorIndexNode descriptorIndexNode) - throws PSTException, IOException - { - super(theFile, descriptorIndexNode); - } - - PSTMessage(PSTFile theFile, DescriptorIndexNode folderIndexNode, PSTTableBC table, HashMap localDescriptorItems) - { - super(theFile, folderIndexNode, table, localDescriptorItems); - } - - public String getRTFBody() - throws PSTException, IOException - { - // do we have an entry for it? - if (this.items.containsKey(0x1009)) - { - // is it a reference? - PSTTableBCItem item = this.items.get(0x1009); - if (item.data.length > 0) { - return (LZFu.decode(item.data)); - } - int ref = item.entryValueReference; - PSTDescriptorItem descItem = this.localDescriptorItems.get(ref); - if ( descItem != null ) { - return LZFu.decode(descItem.getData()); - } - } - - return ""; - } - - - - /** - * get the importance of the email - * @return IMPORTANCE_NORMAL if unknown - */ - public int getImportance() { - return getIntItem(0x0017, IMPORTANCE_NORMAL); - } - - /** - * get the message class for the email - * @return empty string if unknown - */ - public String getMessageClass() { - return this.getStringItem(0x001a); - } - - /** - * get the subject - * @return empty string if not found - */ - public String getSubject() { - String subject = this.getStringItem(0x0037); - -// byte[] controlCodesA = {0x01, 0x01}; -// byte[] controlCodesB = {0x01, 0x05}; -// byte[] controlCodesC = {0x01, 0x10}; - if ( subject != null && - (subject.length() >= 2) && - -// (subject.startsWith(new String(controlCodesA)) || -// subject.startsWith(new String(controlCodesB)) || -// subject.startsWith(new String(controlCodesC))) - subject.charAt(0) == 0x01 ) - { - if ( subject.length() == 2 ) { - subject = ""; - } else { - subject = subject.substring(2, subject.length()); - } - } - return subject; - } - - /** - * get the client submit time - * @return null if not found - */ - public Date getClientSubmitTime() { - return this.getDateItem(0x0039); - } - - /** - * get received by name - * @return empty string if not found - */ - public String getReceivedByName() { - return this.getStringItem(0x0040); - } - - /** - * get sent representing name - * @return empty string if not found - */ - public String getSentRepresentingName() { - return this.getStringItem(0x0042); - } - - /** - * Sent representing address type - * Known values are SMTP, EX (Exchange) and UNKNOWN - * @return empty string if not found - */ - public String getSentRepresentingAddressType() { - return this.getStringItem(0x0064); - } - - /** - * Sent representing email address - * @return empty string if not found - */ - public String getSentRepresentingEmailAddress() { - return this.getStringItem(0x0065); - } - - /** - * Conversation topic - * This is basically the subject from which Fwd:, Re, etc. has been removed - * @return empty string if not found - */ - public String getConversationTopic() { - return this.getStringItem(0x0070); - } - - /** - * Received by address type - * Known values are SMTP, EX (Exchange) and UNKNOWN - * @return empty string if not found - */ - public String getReceivedByAddressType() { - return this.getStringItem(0x0075); - } - - /** - * Received by email address - * @return empty string if not found - */ - public String getReceivedByAddress() { - return this.getStringItem(0x0076); - } - - /** - * Transport message headers ASCII or Unicode string These contain the SMTP e-mail headers. - */ - public String getTransportMessageHeaders() { - return this.getStringItem(0x007d); - } - - - public boolean isRead() { - return ((this.getIntItem(0x0e07) & 0x01) != 0); - } - public boolean isUnmodified() { - return ((this.getIntItem(0x0e07) & 0x02) != 0); - } - public boolean isSubmitted() { - return ((this.getIntItem(0x0e07) & 0x04) != 0); - } - public boolean isUnsent() { - return ((this.getIntItem(0x0e07) & 0x08) != 0); - } - public boolean hasAttachments() { - return ((this.getIntItem(0x0e07) & 0x10) != 0); - } - public boolean isFromMe() { - return ((this.getIntItem(0x0e07) & 0x20) != 0); - } - public boolean isAssociated() { - return ((this.getIntItem(0x0e07) & 0x40) != 0); - } - public boolean isResent() { - return ((this.getIntItem(0x0e07) & 0x80) != 0); - } - - - /** - * Acknowledgment mode Integer 32-bit signed - */ - public int getAcknowledgementMode () { - return this.getIntItem(0x0001); - } - /** - * Originator delivery report requested set if the sender wants a delivery report from all recipients 0 = false 0 != true - */ - public boolean getOriginatorDeliveryReportRequested () { - return (this.getIntItem(0x0023) != 0); - } - // 0x0025 0x0102 PR_PARENT_KEY Parent key Binary data Contains a GUID - /** - * Priority Integer 32-bit signed -1 = NonUrgent 0 = Normal 1 = Urgent - */ - public int getPriority () { - return this.getIntItem(0x0026); - } - /** - * Read Receipt Requested Boolean 0 = false 0 != true - */ - public boolean getReadReceiptRequested () { - return (this.getIntItem(0x0029) != 0); - } - /** - * Recipient Reassignment Prohibited Boolean 0 = false 0 != true - */ - public boolean getRecipientReassignmentProhibited () { - return (this.getIntItem(0x002b) != 0); - } - /** - * Original sensitivity Integer 32-bit signed the sensitivity of the message before being replied to or forwarded 0 = None 1 = Personal 2 = Private 3 = Company Confidential - */ - public int getOriginalSensitivity () { - return this.getIntItem(0x002e); - } - /** - * Sensitivity Integer 32-bit signed sender's opinion of the sensitivity of an email 0 = None 1 = Personal 2 = Private 3 = Company Confidential - */ - public int getSensitivity () { - return this.getIntItem(0x0036); - } - //0x003f 0x0102 PR_RECEIVED_BY_ENTRYID (PidTagReceivedByEntr yId) Received by entry identifier Binary data Contains recipient/sender structure - //0x0041 0x0102 PR_SENT_REPRESENTING_ENTRYID Sent representing entry identifier Binary data Contains recipient/sender structure - //0x0043 0x0102 PR_RCVD_REPRESENTING_ENTRYID Received representing entry identifier Binary data Contains recipient/sender structure - - /* - * Address book search key - */ - public byte[] getPidTagSentRepresentingSearchKey() - { - return this.getBinaryItem(0x003b); - } - /** - * Received representing name ASCII or Unicode string - */ - public String getRcvdRepresentingName () { - return this.getStringItem(0x0044); - } - /** - * Original subject ASCII or Unicode string - */ - public String getOriginalSubject () { - return this.getStringItem(0x0049); - } -// 0x004e 0x0040 PR_ORIGINAL_SUBMIT_TIME Original submit time Filetime - /** - * Reply recipients names ASCII or Unicode string - */ - public String getReplyRecipientNames () { - return this.getStringItem(0x0050); - } - /** - * My address in To field Boolean - */ - public boolean getMessageToMe () { - return (this.getIntItem(0x0057) != 0); - } - /** - * My address in CC field Boolean - */ - public boolean getMessageCcMe () { - return (this.getIntItem(0x0058) != 0); - } - /** - * Message addressed to me ASCII or Unicode string - */ - public String getMessageRecipMe () { - return this.getStringItem(0x0059); - } - /** - * Response requested Boolean - */ - public boolean getResponseRequested () { - return getBooleanItem(0x0063); - } - /** - * Sent representing address type ASCII or Unicode string Known values are SMTP, EX (Exchange) and UNKNOWN - */ - public String getSentRepresentingAddrtype () { - return this.getStringItem(0x0064); - } - //0x0071 0x0102 PR_CONVERSATION_INDEX (PidTagConversationInd ex) Conversation index Binary data - /** - * Original display BCC ASCII or Unicode string - */ - public String getOriginalDisplayBcc () { - return this.getStringItem(0x0072); - } - /** - * Original display CC ASCII or Unicode string - */ - public String getOriginalDisplayCc () { - return this.getStringItem(0x0073); - } - /** - * Original display TO ASCII or Unicode string - */ - public String getOriginalDisplayTo () { - return this.getStringItem(0x0074); - } - /** - * Received representing address type. - * Known values are SMTP, EX (Exchange) and UNKNOWN - */ - public String getRcvdRepresentingAddrtype () { - return this.getStringItem(0x0077); - } - /** - * Received representing e-mail address - */ - public String getRcvdRepresentingEmailAddress() { - return this.getStringItem(0x0078); - } - - /** - * Recipient details - */ - - /** - * Non receipt notification requested - */ - public boolean isNonReceiptNotificationRequested() { - return (this.getIntItem(0x0c06) != 0); - } - - /** - * Originator non delivery report requested - */ - public boolean isOriginatorNonDeliveryReportRequested() { - return (this.getIntItem(0x0c08) != 0); - } - - public static final int RECIPIENT_TYPE_TO = 1; - public static final int RECIPIENT_TYPE_CC = 2; - - /** - * Recipient type Integer 32-bit signed 0x01 => To 0x02 =>CC - */ - public int getRecipientType() { - return this.getIntItem(0x0c15); - } - - /** - * Reply requested - */ - public boolean isReplyRequested() { - return (this.getIntItem(0x0c17) != 0); - } - - /* - * Sending mailbox owner's address book entry ID - */ - public byte[] getSenderEntryId() { - return this.getBinaryItem(0x0c19); - } - - /** - * Sender name - */ - public String getSenderName() { - return this.getStringItem(0x0c1a); - } - - /** - * Sender address type. - * Known values are SMTP, EX (Exchange) and UNKNOWN - */ - public String getSenderAddrtype() { - return this.getStringItem(0x0c1e); - } - - /** - * Sender e-mail address - */ - public String getSenderEmailAddress() { - return this.getStringItem(0x0c1f); - } - - /** - * Non-transmittable message properties - */ - - /** - * Message size - */ - public long getMessageSize() { - return this.getLongItem(0x0e08); - } - /** - * Internet article number - */ - public int getInternetArticleNumber() { - return this.getIntItem(0x0e23); - } - - /* - * Server that the client should attempt to send the mail with - */ - public String getPrimarySendAccount() { - return this.getStringItem(0x0e28); - } - - /* - * Server that the client is currently using to send mail - */ - public String getNextSendAcct() { - return this.getStringItem(0x0e29); - } - - /** - * URL computer name postfix - */ - public int getURLCompNamePostfix() { - return this.getIntItem(0x0e61); - } - /** - * Object type - */ - public int getObjectType() { - return this.getIntItem(0x0ffe); - } - /** - * Delete after submit - */ - public boolean getDeleteAfterSubmit() { - return ((this.getIntItem(0x0e01)) != 0); - } - /** - * Responsibility - */ - public boolean getResponsibility() { - return ((this.getIntItem(0x0e0f)) != 0); - } - /** - * Compressed RTF in Sync Boolean - */ - public boolean isRTFInSync() { - return ((this.getIntItem(0x0e1f)) != 0); - } - /** - * URL computer name set - */ - public boolean isURLCompNameSet() { - return ((this.getIntItem(0x0e62)) != 0); - } - /** - * Display BCC - */ - public String getDisplayBCC() { - return this.getStringItem(0x0e02); - } - /** - * Display CC - */ - public String getDisplayCC() { - return this.getStringItem(0x0e03); - } - /** - * Display To - */ - public String getDisplayTo() { - return this.getStringItem(0x0e04); - } - /** - * Message delivery time - */ - public Date getMessageDeliveryTime() { - return this.getDateItem(0x0e06); - } - -// -// public int getFlags() { -// if (this.items.containsKey(0x0e17)) { -// System.out.println(this.items.get(0x0e17)); -// } -// return this.getIntItem(0x0e17); -// } -// -// /** -// * The message is to be highlighted in recipients' folder displays. -// */ -// public boolean isHighlighted() { -// return (this.getIntItem(0x0e17) & 0x1) != 0; -// } -// -// /** -// * The message has been tagged for a client-defined purpose. -// */ -// public boolean isTagged() { -// return (this.getIntItem(0x0e17) & 0x2) != 0; -// } -// -// /** -// * The message is to be suppressed from recipients' folder displays. -// */ -// public boolean isHidden() { -// return (this.getIntItem(0x0e17) & 0x4) != 0; -// } -// -// /** -// * The message has been marked for subsequent deletion -// */ -// public boolean isDelMarked() { -// return (this.getIntItem(0x0e17) & 0x8) != 0; -// } -// -// /** -// * The message is in draft revision status. -// */ -// public boolean isDraft() { -// return (this.getIntItem(0x0e17) & 0x100) != 0; -// } -// -// /** -// * The message has been replied to. -// */ -// public boolean isAnswered() { -// return (this.getIntItem(0x0e17) & 0x200) != 0; -// } -// -// /** -// * The message has been marked for downloading from the remote message store to the local client -// */ -// public boolean isMarkedForDownload() { -// return (this.getIntItem(0x0e17) & 0x1000) != 0; -// } -// -// /** -// * The message has been marked for deletion at the remote message store without downloading to the local client. -// */ -// public boolean isRemoteDelMarked() { -// return (this.getIntItem(0x0e17) & 0x2000) != 0; -// } - - /** - * Message content properties - */ - - /** - * Plain text e-mail body - */ - public String getBody() { - String cp = null; - PSTTableBCItem cpItem = this.items.get(0x3FFD); // PidTagMessageCodepage - if (cpItem == null) { - cpItem = this.items.get(0x3FDE); // PidTagInternetCodepage - } - if (cpItem != null) { - cp = PSTFile.getInternetCodePageCharset(cpItem.entryValueReference); - } - return this.getStringItem(0x1000, 0, cp); - } - /* - * Plain text body prefix - */ - public String getBodyPrefix() { - return this.getStringItem(0x6619); - } - /** - * RTF Sync Body CRC - */ - public int getRTFSyncBodyCRC() { - return this.getIntItem(0x1006); - } - /** - * RTF Sync Body character count - */ - public int getRTFSyncBodyCount() { - return this.getIntItem(0x1007); - } - /** - * RTF Sync body tag - */ - public String getRTFSyncBodyTag() { - return this.getStringItem(0x1008); - } - /** - * RTF whitespace prefix count - */ - public int getRTFSyncPrefixCount() { - return this.getIntItem(0x1010); - } - /** - * RTF whitespace tailing count - */ - public int getRTFSyncTrailingCount() { - return this.getIntItem(0x1011); - } - /** - * HTML e-mail body - */ - public String getBodyHTML() { - String cp = null; - PSTTableBCItem cpItem = this.items.get(0x3FDE); // PidTagInternetCodepage - if (cpItem == null) { - cpItem = this.items.get(0x3FFD); // PidTagMessageCodepage - } - if (cpItem != null) { - cp = PSTFile.getInternetCodePageCharset(cpItem.entryValueReference); - } - return this.getStringItem(0x1013, 0, cp); - } - - /** - * Message ID for this email as allocated per rfc2822 - */ - public String getInternetMessageId() { - return this.getStringItem(0x1035); - } - /** - * In-Reply-To - */ - public String getInReplyToId() { - return this.getStringItem(0x1042); - } - /** - * Return Path - */ - public String getReturnPath() { - return this.getStringItem(0x1046); - } - /** - * Icon index - */ - public int getIconIndex() { - return this.getIntItem(0x1080); - } - - /** - * Action flag - * This relates to the replying / forwarding of messages. - * It is classified as "unknown" atm, so just provided here - * in case someone works out what all the various flags mean. - */ - public int getActionFlag() { - return this.getIntItem(0x1081); - } - /** - * is the action flag for this item "forward"? - */ - public boolean hasForwarded() { - int actionFlag = this.getIntItem(0x1081); - return ((actionFlag & 0x8) > 0); - } - /** - * is the action flag for this item "replied"? - */ - public boolean hasReplied() { - int actionFlag = this.getIntItem(0x1081); - return ((actionFlag & 0x4) > 0); - } - /** - * the date that this item had an action performed (eg. replied or forwarded) - */ - public Date getActionDate() { - return this.getDateItem(0x1082); - } - - /** - * Disable full fidelity - */ - public boolean getDisableFullFidelity() { - return (this.getIntItem(0x10f2) != 0); - } - /** - * URL computer name - * Contains the .eml file name - */ - public String getURLCompName() { - return this.getStringItem(0x10f3); - } - /** - * Attribute hidden - */ - public boolean getAttrHidden() { - return (this.getIntItem(0x10f4) != 0); - } - /** - * Attribute system - */ - public boolean getAttrSystem() { - return (this.getIntItem(0x10f5) != 0); - } - /** - * Attribute read only - */ - public boolean getAttrReadonly() { - return (this.getIntItem(0x10f6) != 0); - } - - - private PSTTable7C recipientTable = null; - /** - * find, extract and load up all of the attachments in this email - * necessary for the other operations. - * @throws PSTException - * @throws IOException - */ - private void processRecipients() - { - try { - int recipientTableKey = 0x0692; - if (this.recipientTable == null && - this.localDescriptorItems != null && - this.localDescriptorItems.containsKey(recipientTableKey)) - { - PSTDescriptorItem item = this.localDescriptorItems.get(recipientTableKey); - HashMap descriptorItems = null; - if (item.subNodeOffsetIndexIdentifier > 0) { - descriptorItems =pstFile.getPSTDescriptorItems(item.subNodeOffsetIndexIdentifier); - } - recipientTable = new PSTTable7C(new PSTNodeInputStream(pstFile, item), descriptorItems); - } - } catch ( Exception e ) { - e.printStackTrace(); - recipientTable = null; - } - } - - /** - * get the number of recipients for this message - * @throws PSTException - * @throws IOException - */ - public int getNumberOfRecipients() - throws PSTException, IOException - { - this.processRecipients(); - - // still nothing? must be no recipients... - if ( this.recipientTable == null ) { - return 0; - } - return this.recipientTable.getRowCount(); - } - - /** - * attachment stuff here, not sure if these can just exist in emails or not, - * but a table key of 0x0671 would suggest that this is a property of the envelope - * rather than a specific email property - */ - - private PSTTable7C attachmentTable = null; - - /** - * find, extract and load up all of the attachments in this email - * necessary for the other operations. - * @throws PSTException - * @throws IOException - */ - private void processAttachments() - throws PSTException, IOException - { - int attachmentTableKey = 0x0671; - if (this.attachmentTable == null && - this.localDescriptorItems != null && - this.localDescriptorItems.containsKey(attachmentTableKey)) - { - PSTDescriptorItem item = this.localDescriptorItems.get(attachmentTableKey); - HashMap descriptorItems = null; - if (item.subNodeOffsetIndexIdentifier > 0) { - descriptorItems =pstFile.getPSTDescriptorItems(item.subNodeOffsetIndexIdentifier); - } - attachmentTable = new PSTTable7C(new PSTNodeInputStream(pstFile, item), descriptorItems); - } - } - - /** - * Start date Filetime - */ - public Date getTaskStartDate() { - return getDateItem(pstFile.getNameToIdMapItem(0x00008104, PSTFile.PSETID_Task)); - } - /** - * Due date Filetime - */ - public Date getTaskDueDate() { - return getDateItem(pstFile.getNameToIdMapItem(0x00008105, PSTFile.PSETID_Task)); - } - - /** - * Is a reminder set on this object? - * @return - */ - public boolean getReminderSet() { - return getBooleanItem(pstFile.getNameToIdMapItem(0x00008503, PSTFile.PSETID_Common)); - } - - public int getReminderDelta() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008501, PSTFile.PSETID_Common)); - } - - /** - * "flagged" items are actually emails with a due date. - * This convience method just checks to see if that is true. - */ - public boolean isFlagged() { - return getTaskDueDate() != null; - } - - /** - * get the categories defined for this message - */ - public String[] getColorCategories() - throws PSTException - { - int keywordCategory = pstFile.getPublicStringToIdMapItem("Keywords"); - - String[] categories = new String[0]; - if (this.items.containsKey(keywordCategory)) { - try { - PSTTableBCItem item = this.items.get(keywordCategory); - if (item.data.length == 0) { - return categories; - } - int categoryCount = item.data[0]; - if (categoryCount > 0) { - categories = new String[categoryCount]; - int[] offsets = new int[categoryCount]; - for (int x = 0; x < categoryCount; x++) { - offsets[x] = (int)PSTObject.convertBigEndianBytesToLong(item.data, (x*4)+1, (x+1)*4+1); - } - for (int x = 0; x < offsets.length -1; x++) { - int start = offsets[x]; - int end = offsets[x+1]; - int length = (end-start); - byte[] string = new byte[length]; - System.arraycopy(item.data, start, string, 0, length); - String name = new String(string, "UTF-16LE"); - categories[x] = name; - } - int start = offsets[offsets.length-1]; - int end = item.data.length; - int length = (end-start); - byte[] string = new byte[length]; - System.arraycopy(item.data, start, string, 0, length); - String name = new String(string, "UTF-16LE"); - categories[categories.length-1] = name; - } - } catch (Exception err) { - throw new PSTException("Unable to decode category data", err); - } - } - return categories; - } - - /** - * get the number of attachments for this message - * @throws PSTException - * @throws IOException - */ - public int getNumberOfAttachments() - { - try { - this.processAttachments(); - } catch (Exception e) { - e.printStackTrace(); - return 0; - } - - - // still nothing? must be no attachments... - if ( this.attachmentTable == null ) { - return 0; - } - return this.attachmentTable.getRowCount(); - } - - /** - * get a specific attachment from this email. - * @param attachmentNumber - * @return the attachment at the defined index - * @throws PSTException - * @throws IOException - */ - public PSTAttachment getAttachment(int attachmentNumber) - throws PSTException, IOException - { - this.processAttachments(); - - int attachmentCount = 0; - if ( this.attachmentTable != null ) { - attachmentCount = this.attachmentTable.getRowCount(); - } - - if (attachmentNumber >= attachmentCount) { - throw new PSTException("unable to fetch attachment number "+attachmentNumber+", only "+attachmentCount+" in this email"); - } - - // we process the C7 table here, basically we just want the attachment local descriptor... - HashMap attachmentDetails = this.attachmentTable.getItems().get(attachmentNumber); - PSTTable7CItem attachmentTableItem = attachmentDetails.get(0x67f2); - int descriptorItemId = attachmentTableItem.entryValueReference; - - // get the local descriptor for the attachmentDetails table. - PSTDescriptorItem descriptorItem = this.localDescriptorItems.get(descriptorItemId); - - // try and decode it - byte[] attachmentData = descriptorItem.getData(); - if ( attachmentData != null && attachmentData.length > 0 ) { - //PSTTableBC attachmentDetailsTable = new PSTTableBC(descriptorItem.getData(), descriptorItem.getBlockOffsets()); - PSTTableBC attachmentDetailsTable = new PSTTableBC(new PSTNodeInputStream(pstFile, descriptorItem)); - - // create our all-precious attachment object. - // note that all the information that was in the c7 table is repeated in the eb table in attachment data. - // so no need to pass it... - HashMap attachmentDescriptorItems = new HashMap(); - if (descriptorItem.subNodeOffsetIndexIdentifier > 0) { - attachmentDescriptorItems = pstFile.getPSTDescriptorItems(descriptorItem.subNodeOffsetIndexIdentifier); - } - return new PSTAttachment(this.pstFile, attachmentDetailsTable, attachmentDescriptorItems); - } - - throw new PSTException("unable to fetch attachment number "+attachmentNumber+", unable to read attachment details table"); - } - - /** - * get a specific recipient from this email. - * @param recipientNumber - * @return the recipient at the defined index - * @throws PSTException - * @throws IOException - */ - public PSTRecipient getRecipient(int recipientNumber) - throws PSTException, IOException - { - if ( recipientNumber >= getNumberOfRecipients() || recipientNumber >= recipientTable.getItems().size() ) - { - throw new PSTException("unable to fetch recipient number "+recipientNumber); - } - - HashMap recipientDetails = recipientTable.getItems().get(recipientNumber); - - if ( recipientDetails != null ) { - return new PSTRecipient(recipientDetails); - } - - return null; - } - - - public String getRecipientsString() { - if ( recipientTable != null ) { - return recipientTable.getItemsString(); - } - - return "No recipients table!"; - } - - - /** - * string representation of this email - */ - public String toString() { - return - "PSTEmail: "+this.getSubject()+"\n"+ - "Importance: "+this.getImportance()+"\n"+ - "Message Class: "+this.getMessageClass() + "\n\n" + - this.getTransportMessageHeaders()+"\n\n\n"+ - this.items+ - this.localDescriptorItems; - } - -} diff --git a/com/pff/PSTMessageStore.java b/com/pff/PSTMessageStore.java deleted file mode 100644 index 553593f..0000000 --- a/com/pff/PSTMessageStore.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * 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.*; - - -/** - * Object that represents the message store. - * Not much use other than to get the "name" of the PST file. - * @author Richard Johnson - */ -public class PSTMessageStore extends PSTObject { - - PSTMessageStore(PSTFile theFile, DescriptorIndexNode descriptorIndexNode) - throws PSTException, IOException - { - super(theFile, descriptorIndexNode); - } - - /** - * Get the tag record key, unique to this pst - */ - public UUID getTagRecordKeyAsUUID() { - // attempt to find in the table. - int guidEntryType = 0x0ff9; - if (this.items.containsKey(guidEntryType)) { - PSTTableBCItem item = this.items.get(guidEntryType); - int offset = 0; - byte[] bytes = item.data; - long mostSigBits = (PSTObject.convertLittleEndianBytesToLong(bytes, offset, offset+4) << 32) | - (PSTObject.convertLittleEndianBytesToLong(bytes, offset+4, offset+6) << 16) | - PSTObject.convertLittleEndianBytesToLong(bytes, offset+6, offset+8); - long leastSigBits = PSTObject.convertBigEndianBytesToLong(bytes, offset+8, offset+16); - return new UUID(mostSigBits, leastSigBits); - } - return null; - } - - /** - * get the message store display name - */ - public String getDisplayName() { - // attempt to find in the table. - int displayNameEntryType = 0x3001; - if (this.items.containsKey(displayNameEntryType)) { - return this.getStringItem(displayNameEntryType); - //PSTTableBCItem item = (PSTTableBCItem)this.items.get(displayNameEntryType); - //return new String(item.getStringValue()); - } - return ""; - } - - - public String getDetails() { - return this.items.toString(); - } - -} diff --git a/com/pff/PSTNodeInputStream.java b/com/pff/PSTNodeInputStream.java deleted file mode 100644 index 3c36218..0000000 --- a/com/pff/PSTNodeInputStream.java +++ /dev/null @@ -1,450 +0,0 @@ -/* - * 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; - encrypted = pstFile.getEncryptionType() == PSTFile.ENCRYPTION_TYPE_COMPRESSIBLE; - this.currentBlock = 0; - this.currentLocation = 0; - } - PSTNodeInputStream(PSTFile pstFile, byte[] attachmentData, boolean encrypted) { - this.allData = attachmentData; - this.encrypted = encrypted; - this.length = this.allData.length; - 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; - } - -} diff --git a/com/pff/PSTObject.java b/com/pff/PSTObject.java deleted file mode 100644 index c89aa48..0000000 --- a/com/pff/PSTObject.java +++ /dev/null @@ -1,812 +0,0 @@ -/** - * 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.IOException; -import java.util.*; - -/** - * PST Object is the root class of all PST Items. - * It also provides a number of static utility functions. The most important of which is the - * detectAndLoadPSTObject call which allows extraction of a PST Item from the file. - * @author Richard Johnson - */ -public class PSTObject { - - public static final int NID_TYPE_HID = 0x00; // Heap node - public static final int NID_TYPE_INTERNAL = 0x01; // Internal node (section 2.4.1) - public static final int NID_TYPE_NORMAL_FOLDER = 0x02; // Normal Folder object (PC) - public static final int NID_TYPE_SEARCH_FOLDER = 0x03; // Search Folder object (PC) - public static final int NID_TYPE_NORMAL_MESSAGE = 0x04; // Normal Message object (PC) - public static final int NID_TYPE_ATTACHMENT = 0x05; // Attachment object (PC) - public static final int NID_TYPE_SEARCH_UPDATE_QUEUE = 0x06; // Queue of changed objects for search Folder objects - public static final int NID_TYPE_SEARCH_CRITERIA_OBJECT = 0x07; // Defines the search criteria for a search Folder object - public static final int NID_TYPE_ASSOC_MESSAGE = 0x08; // Folder associated information (FAI) Message object (PC) - public static final int NID_TYPE_CONTENTS_TABLE_INDEX = 0x0A; // Internal, persisted view-related - public static final int NID_TYPE_RECEIVE_FOLDER_TABLE = 0X0B; // Receive Folder object (Inbox) - public static final int NID_TYPE_OUTGOING_QUEUE_TABLE = 0x0C; // Outbound queue (Outbox) - public static final int NID_TYPE_HIERARCHY_TABLE = 0x0D; // Hierarchy table (TC) - public static final int NID_TYPE_CONTENTS_TABLE = 0x0E; // Contents table (TC) - public static final int NID_TYPE_ASSOC_CONTENTS_TABLE = 0x0F; // FAI contents table (TC) - public static final int NID_TYPE_SEARCH_CONTENTS_TABLE = 0x10; // Contents table (TC) of a search Folder object - public static final int NID_TYPE_ATTACHMENT_TABLE = 0x11; // Attachment table (TC) - public static final int NID_TYPE_RECIPIENT_TABLE = 0x12; // Recipient table (TC) - public static final int NID_TYPE_SEARCH_TABLE_INDEX = 0x13; // Internal, persisted view-related - public static final int NID_TYPE_LTP = 0x1F; // LTP - - - - public String getItemsString() { - return items.toString(); - } - - protected PSTFile pstFile; - protected byte[] data; - protected DescriptorIndexNode descriptorIndexNode; - protected HashMap items; - protected HashMap localDescriptorItems = null; - - protected LinkedHashMap> children; - - protected PSTObject(PSTFile theFile, DescriptorIndexNode descriptorIndexNode) - throws PSTException, IOException - { - this.pstFile = theFile; - this.descriptorIndexNode = descriptorIndexNode; - - //descriptorIndexNode.readData(theFile); - //PSTTableBC table = new PSTTableBC(descriptorIndexNode.dataBlock.data, descriptorIndexNode.dataBlock.blockOffsets); - PSTTableBC table = new PSTTableBC(new PSTNodeInputStream(pstFile, pstFile.getOffsetIndexNode(descriptorIndexNode.dataOffsetIndexIdentifier))); - //System.out.println(table); - this.items = table.getItems(); - - if (descriptorIndexNode.localDescriptorsOffsetIndexIdentifier != 0) { - //PSTDescriptor descriptor = new PSTDescriptor(theFile, descriptorIndexNode.localDescriptorsOffsetIndexIdentifier); - //localDescriptorItems = descriptor.getChildren(); - this.localDescriptorItems = theFile.getPSTDescriptorItems(descriptorIndexNode.localDescriptorsOffsetIndexIdentifier); - } - } - - /** - * for pre-population - * @param theFile - * @param folderIndexNode - * @param table - */ - protected PSTObject(PSTFile theFile, DescriptorIndexNode folderIndexNode, PSTTableBC table, HashMap localDescriptorItems) { - this.pstFile = theFile; - this.descriptorIndexNode = folderIndexNode; - this.items = table.getItems(); - this.table = table; - this.localDescriptorItems = localDescriptorItems; - } - protected PSTTableBC table; - - - - /** - * get the descriptor node for this item - * this identifies the location of the node in the BTree and associated info - * @return item's descriptor node - */ - public DescriptorIndexNode getDescriptorNode() { - return this.descriptorIndexNode; - } - /** - * get the descriptor identifier for this item - * can be used for loading objects through detectAndLoadPSTObject(PSTFile theFile, long descriptorIndex) - * @return item's descriptor node identifier - */ - public long getDescriptorNodeId() { - return this.descriptorIndexNode.descriptorIdentifier; - } - - public int getNodeType() { - return PSTObject.getNodeType(this.descriptorIndexNode.descriptorIdentifier); - } - public static int getNodeType(int descriptorIdentifier) { - return descriptorIdentifier & 0x1F; - } - - - protected int getIntItem(int identifier) { - return getIntItem(identifier, 0); - } - protected int getIntItem(int identifier, int defaultValue) { - if (this.items.containsKey(identifier)) { - PSTTableBCItem item = this.items.get(identifier); - return item.entryValueReference; - } - return defaultValue; - } - - protected boolean getBooleanItem(int identifier) { - return getBooleanItem(identifier, false); - } - protected boolean getBooleanItem(int identifier, boolean defaultValue) { - if (this.items.containsKey(identifier)) { - PSTTableBCItem item = this.items.get(identifier); - return item.entryValueReference != 0; - } - return defaultValue; - } - - protected double getDoubleItem(int identifier) { - return getDoubleItem(identifier, 0); - } - protected double getDoubleItem(int identifier, double defaultValue) { - if (this.items.containsKey(identifier)) { - PSTTableBCItem item = this.items.get(identifier); - long longVersion = PSTObject.convertLittleEndianBytesToLong(item.data); - return Double.longBitsToDouble(longVersion); - } - return defaultValue; - } - - - protected long getLongItem(int identifier) { - return getLongItem(identifier, 0); - } - protected long getLongItem(int identifier, long defaultValue) { - if (this.items.containsKey(identifier)) { - PSTTableBCItem item = this.items.get(identifier); - if (item.entryValueType == 0x0003) { - // we are really just an int - return item.entryValueReference; - } else if ( item.entryValueType == 0x0014 ){ - // we are a long - if ( item.data != null && item.data.length == 8 ) { - return PSTObject.convertLittleEndianBytesToLong(item.data, 0, 8); - } else { - System.err.printf("Invalid data length for long id 0x%04X\n", identifier); - // Return the default value for now... - } - } - } - return defaultValue; - } - - protected String getStringItem(int identifier) { - return getStringItem(identifier, 0); - } - protected String getStringItem(int identifier, int stringType) { - return getStringItem(identifier, stringType, null); - } - protected String getStringItem(int identifier, int stringType, String codepage) { - PSTTableBCItem item = this.items.get(identifier); - if ( item != null ) { - - if (codepage == null) { - codepage = this.getStringCodepage(); - } - - // get the string type from the item if not explicitly set - if ( stringType == 0 ) { - stringType = item.entryValueType; - } - - // see if there is a descriptor entry - if ( !item.isExternalValueReference ) { - //System.out.println("here: "+new String(item.data)+this.descriptorIndexNode.descriptorIdentifier); - return PSTObject.createJavaString(item.data, stringType, codepage); - } - if (this.localDescriptorItems != null && - this.localDescriptorItems.containsKey(item.entryValueReference)) - { - // we have a hit! - PSTDescriptorItem descItem = this.localDescriptorItems.get(item.entryValueReference); - - try { - byte[] data = descItem.getData(); - if ( data == null ) { - return ""; - } - - return PSTObject.createJavaString(data, stringType, codepage); - } catch (Exception e) { - System.err.printf("Exception %s decoding string %s: %s\n", - e.toString(), - PSTFile.getPropertyDescription(identifier, stringType), data != null ? data.toString() : "null"); - return ""; - } - //System.out.printf("PSTObject.getStringItem - item isn't a string: 0x%08X\n", identifier); - //return ""; - } - - return PSTObject.createJavaString(data, stringType, codepage); - } - return ""; - } - - static String createJavaString(byte[] data, int stringType, String codepage) - { - try { - if ( stringType == 0x1F ) { - return new String(data, "UTF-16LE"); - } - - if (codepage == null) { - return new String(data); - } else { - codepage = codepage.toUpperCase(); - return new String(data, codepage); - } - /* - if (codepage == null || codepage.toUpperCase().equals("UTF-8") || codepage.toUpperCase().equals("UTF-7")) { - // PST UTF-8 strings are not... really UTF-8 - // it seems that they just don't use multibyte chars at all. - // indeed, with some crylic chars in there, the difficult chars are just converted to %3F(?) - // I suspect that outlook actually uses RTF to store these problematic strings. - StringBuffer sbOut = new StringBuffer(); - for (int x = 0; x < data.length; x++) { - sbOut.append((char)(data[x] & 0xFF)); // just blindly accept the byte as a UTF char, seems right half the time - } - return new String(sbOut); - } else { - codepage = codepage.toUpperCase(); - return new String(data, codepage); - } - */ - } catch (Exception err) { - System.err.println("Unable to decode string"); - err.printStackTrace(); - return ""; - } - } - - private String getStringCodepage() { - // try and get the codepage - PSTTableBCItem cpItem = this.items.get(0x3FFD); // PidTagMessageCodepage - if (cpItem == null) { - cpItem = this.items.get(0x3FDE); // PidTagInternetCodepage - } - if (cpItem != null) { - return PSTFile.getInternetCodePageCharset(cpItem.entryValueReference); - } - return null; - } - - public Date getDateItem(int identifier) { - if ( this.items.containsKey(identifier) ) { - PSTTableBCItem item = this.items.get(identifier); - if (item.data.length == 0 ) { - return new Date(0); - } - int high = (int)PSTObject.convertLittleEndianBytesToLong(item.data, 4, 8); - int low = (int)PSTObject.convertLittleEndianBytesToLong(item.data, 0, 4); - - return PSTObject.filetimeToDate(high, low); - } - return null; - } - - protected byte[] getBinaryItem(int identifier) { - if (this.items.containsKey(identifier)) { - PSTTableBCItem item = this.items.get(identifier); - if ( item.entryValueType == 0x0102 ) { - if ( !item.isExternalValueReference ) { - return item.data; - } - if ( this.localDescriptorItems != null && - this.localDescriptorItems.containsKey(item.entryValueReference)) - { - // we have a hit! - PSTDescriptorItem descItem = this.localDescriptorItems.get(item.entryValueReference); - try { - return descItem.getData(); - } catch (Exception e) { - System.err.printf("Exception reading binary item: reference 0x%08X\n", item.entryValueReference); - - return null; - } - } - - //System.out.println("External reference!!!\n"); - } - } - return null; - } - - protected PSTTimeZone getTimeZoneItem(int identifier) { - byte[] tzData = getBinaryItem(identifier); - if ( tzData != null && tzData.length != 0 ) { - return new PSTTimeZone(tzData); - } - return null; - } - - public String getMessageClass() { - return this.getStringItem(0x001a); - } - - public String toString() { - return this.localDescriptorItems + "\n" + - (this.items); - } - - /** - * These are the common properties, some don't really appear to be common across folders and emails, but hey - */ - - /** - * get the display name - */ - public String getDisplayName() { - return this.getStringItem(0x3001); - } - /** - * Address type - * Known values are SMTP, EX (Exchange) and UNKNOWN - */ - public String getAddrType() { - return this.getStringItem(0x3002); - } - /** - * E-mail address - */ - public String getEmailAddress() { - return this.getStringItem(0x3003); - } - /** - * Comment - */ - public String getComment() { - return this.getStringItem(0x3004); - } - /** - * Creation time - */ - public Date getCreationTime() { - return this.getDateItem(0x3007); - } - /** - * Modification time - */ - public Date getLastModificationTime() { - return this.getDateItem(0x3008); - } - - - /** - * Static stuff below - * ------------------ - */ - - // substitution table for the compressible encryption type. - static int[] compEnc = { - 0x47, 0xf1, 0xb4, 0xe6, 0x0b, 0x6a, 0x72, 0x48, 0x85, 0x4e, 0x9e, 0xeb, 0xe2, 0xf8, 0x94, 0x53, - 0xe0, 0xbb, 0xa0, 0x02, 0xe8, 0x5a, 0x09, 0xab, 0xdb, 0xe3, 0xba, 0xc6, 0x7c, 0xc3, 0x10, 0xdd, - 0x39, 0x05, 0x96, 0x30, 0xf5, 0x37, 0x60, 0x82, 0x8c, 0xc9, 0x13, 0x4a, 0x6b, 0x1d, 0xf3, 0xfb, - 0x8f, 0x26, 0x97, 0xca, 0x91, 0x17, 0x01, 0xc4, 0x32, 0x2d, 0x6e, 0x31, 0x95, 0xff, 0xd9, 0x23, - 0xd1, 0x00, 0x5e, 0x79, 0xdc, 0x44, 0x3b, 0x1a, 0x28, 0xc5, 0x61, 0x57, 0x20, 0x90, 0x3d, 0x83, - 0xb9, 0x43, 0xbe, 0x67, 0xd2, 0x46, 0x42, 0x76, 0xc0, 0x6d, 0x5b, 0x7e, 0xb2, 0x0f, 0x16, 0x29, - 0x3c, 0xa9, 0x03, 0x54, 0x0d, 0xda, 0x5d, 0xdf, 0xf6, 0xb7, 0xc7, 0x62, 0xcd, 0x8d, 0x06, 0xd3, - 0x69, 0x5c, 0x86, 0xd6, 0x14, 0xf7, 0xa5, 0x66, 0x75, 0xac, 0xb1, 0xe9, 0x45, 0x21, 0x70, 0x0c, - 0x87, 0x9f, 0x74, 0xa4, 0x22, 0x4c, 0x6f, 0xbf, 0x1f, 0x56, 0xaa, 0x2e, 0xb3, 0x78, 0x33, 0x50, - 0xb0, 0xa3, 0x92, 0xbc, 0xcf, 0x19, 0x1c, 0xa7, 0x63, 0xcb, 0x1e, 0x4d, 0x3e, 0x4b, 0x1b, 0x9b, - 0x4f, 0xe7, 0xf0, 0xee, 0xad, 0x3a, 0xb5, 0x59, 0x04, 0xea, 0x40, 0x55, 0x25, 0x51, 0xe5, 0x7a, - 0x89, 0x38, 0x68, 0x52, 0x7b, 0xfc, 0x27, 0xae, 0xd7, 0xbd, 0xfa, 0x07, 0xf4, 0xcc, 0x8e, 0x5f, - 0xef, 0x35, 0x9c, 0x84, 0x2b, 0x15, 0xd5, 0x77, 0x34, 0x49, 0xb6, 0x12, 0x0a, 0x7f, 0x71, 0x88, - 0xfd, 0x9d, 0x18, 0x41, 0x7d, 0x93, 0xd8, 0x58, 0x2c, 0xce, 0xfe, 0x24, 0xaf, 0xde, 0xb8, 0x36, - 0xc8, 0xa1, 0x80, 0xa6, 0x99, 0x98, 0xa8, 0x2f, 0x0e, 0x81, 0x65, 0x73, 0xe4, 0xc2, 0xa2, 0x8a, - 0xd4, 0xe1, 0x11, 0xd0, 0x08, 0x8b, 0x2a, 0xf2, 0xed, 0x9a, 0x64, 0x3f, 0xc1, 0x6c, 0xf9, 0xec - }; - - /** - * Output a dump of data in hex format in the order it was read in - * @param data - * @param pretty - */ - public static void printHexFormatted(byte[] data, boolean pretty) { - printHexFormatted(data,pretty, new int[0]); - } - protected static void printHexFormatted(byte[] data, boolean pretty, int[] indexes) { - // groups of two - if (pretty) { System.out.println("---"); } - long tmpLongValue; - String line = ""; - int nextIndex = 0; - int indexIndex = 0; - if (indexes.length > 0) { - nextIndex = indexes[0]; - indexIndex++; - } - for (int x = 0; x < data.length; x++) { - tmpLongValue = (long)data[x] & 0xff; - - if (indexes.length > 0 && - x == nextIndex && - nextIndex < data.length) - { - System.out.print("+"); - line += "+"; - while (indexIndex < indexes.length-1 && indexes[indexIndex] <= nextIndex) - { - indexIndex++; - } - nextIndex = indexes[indexIndex]; - //indexIndex++; - } - - if (Character.isLetterOrDigit((char)tmpLongValue)) { - line += (char)tmpLongValue; - } - else - { - line += "."; - } - - if (Long.toHexString(tmpLongValue).length() < 2) { - System.out.print("0"); - } - System.out.print(Long.toHexString(tmpLongValue)); - if (x % 2 == 1 && pretty) { - System.out.print(" "); - } - if (x % 16 == 15 && pretty) { - System.out.print(" "+line); - System.out.println(""); - line = ""; - } - } - if (pretty) { System.out.println(" "+line); System.out.println("---"); System.out.println(data.length); } else { } - } - - - - /** - * decode a lump of data that has been encrypted with the compressible encryption - * @param data - * @return decoded data - */ - protected static byte[] decode(byte[] data) { - int temp; - for (int x = 0; x < data.length; x++) { - temp = data[x] & 0xff; - data[x] = (byte)compEnc[temp]; - } - - return data; - } - - - protected static byte[] encode(byte[] data) { - // create the encoding array... - int[] enc = new int[compEnc.length]; - for (int x = 0; x < enc.length; x++) { - enc[compEnc[x]] = x; - } - - // now it's just the same as decode... - int temp; - for (int x = 0; x < data.length; x++) { - temp = data[x] & 0xff; - data[x] = (byte)enc[temp]; - } - - return data; - } - - - /** - * Utility function for converting little endian bytes into a usable java long - * @param data - * @return long version of the data - */ - public static long convertLittleEndianBytesToLong(byte[] data) { - return convertLittleEndianBytesToLong(data, 0, data.length); - } - /** - * Utility function for converting little endian bytes into a usable java long - * @param data - * @param start - * @param end - * @return long version of the data - */ - public static long convertLittleEndianBytesToLong(byte[] data, int start, int end) { - - long offset = data[end-1] & 0xff; - long tmpLongValue; - for (int x = end-2; x >= start; x--) { - offset = offset << 8; - tmpLongValue = (long)data[x] & 0xff; - offset |= tmpLongValue; - } - - return offset; - } - - /** - * Utility function for converting big endian bytes into a usable java long - * @param data - * @param start - * @param end - * @return long version of the data - */ - public static long convertBigEndianBytesToLong(byte[] data, int start, int end) { - - long offset = 0; - for ( int x = start; x < end; ++x ) { - offset = offset << 8; - offset |= ((long)data[x] & 0xFFL); - } - - return offset; - } -/* - protected static boolean isPSTArray(byte[] data) { - return (data[0] == 1 && data[1] == 1); - } -/**/ -/* - protected static int[] getBlockOffsets(RandomAccessFile in, byte[] data) - throws IOException, PSTException - { - // is the data an array? - if (!(data[0] == 1 && data[1] == 1)) - { - throw new PSTException("Unable to process array, does not appear to be one!"); - } - - // we are an array! - // get the array items and merge them together - int numberOfEntries = (int)PSTObject.convertLittleEndianBytesToLong(data, 2, 4); - int[] output = new int[numberOfEntries]; - int tableOffset = 8; - int blockOffset = 0; - for (int y = 0; y < numberOfEntries; y++) { - // get the offset identifier - long tableOffsetIdentifierIndex = PSTObject.convertLittleEndianBytesToLong(data, tableOffset, tableOffset+8); - // clear the last bit of the identifier. Why so hard? - tableOffsetIdentifierIndex = (tableOffsetIdentifierIndex & 0xfffffffe); - OffsetIndexItem tableOffsetIdentifier = PSTObject.getOffsetIndexNode(in, tableOffsetIdentifierIndex); - blockOffset += tableOffsetIdentifier.size; - output[y] = blockOffset; - tableOffset += 8; - } - - // replace the item data with the stuff from the array... - return output; - } -/**/ - - /** - * Detect and load a PST Object from a file with the specified descriptor index - * @param theFile - * @param descriptorIndex - * @return PSTObject with that index - * @throws IOException - * @throws PSTException - */ - public static PSTObject detectAndLoadPSTObject(PSTFile theFile, long descriptorIndex) - throws IOException, PSTException - { - return PSTObject.detectAndLoadPSTObject(theFile, theFile.getDescriptorIndexNode(descriptorIndex)); - } - - /** - * Detect and load a PST Object from a file with the specified descriptor index - * @param theFile - * @param folderIndexNode - * @return PSTObject with that index - * @throws IOException - * @throws PSTException - */ - static PSTObject detectAndLoadPSTObject(PSTFile theFile, DescriptorIndexNode folderIndexNode) - throws IOException, PSTException - { - int nidType = (folderIndexNode.descriptorIdentifier & 0x1F); - if ( nidType == 0x02 || nidType == 0x03 || nidType == 0x04 ) { - - PSTTableBC table = new PSTTableBC(new PSTNodeInputStream(theFile, theFile.getOffsetIndexNode(folderIndexNode.dataOffsetIndexIdentifier))); - - HashMap localDescriptorItems = null; - if (folderIndexNode.localDescriptorsOffsetIndexIdentifier != 0) { - localDescriptorItems = theFile.getPSTDescriptorItems(folderIndexNode.localDescriptorsOffsetIndexIdentifier); - } - - if ( nidType == 0x02 || nidType == 0x03 ) { - return new PSTFolder(theFile, folderIndexNode, table, localDescriptorItems); - } else { - return PSTObject.createAppropriatePSTMessageObject(theFile, folderIndexNode, table, localDescriptorItems); - } - } - else - { - throw new PSTException("Unknown child type with offset id: "+folderIndexNode.localDescriptorsOffsetIndexIdentifier); - } - } - - static PSTMessage createAppropriatePSTMessageObject(PSTFile theFile, DescriptorIndexNode folderIndexNode, PSTTableBC table, HashMap localDescriptorItems) - { - - PSTTableBCItem item = table.getItems().get(0x001a); - String messageClass = ""; - if ( item != null ) - { - messageClass = item.getStringValue(); - } - - if (messageClass.equals("IPM.Note")) { - return new PSTMessage(theFile, folderIndexNode, table, localDescriptorItems); - } else if (messageClass.equals("IPM.Appointment") || - messageClass.equals("IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}") || - messageClass.startsWith("IPM.Schedule.Meeting")) { - return new PSTAppointment(theFile, folderIndexNode, table, localDescriptorItems); - } else if (messageClass.equals("IPM.Contact")) { - return new PSTContact(theFile, folderIndexNode, table, localDescriptorItems); - } else if (messageClass.equals("IPM.Task")) { - return new PSTTask(theFile, folderIndexNode, table, localDescriptorItems); - } else if (messageClass.equals("IPM.Activity")) { - return new PSTActivity(theFile, folderIndexNode, table, localDescriptorItems); - } else if (messageClass.equals("IPM.Post.Rss")) { - return new PSTRss(theFile, folderIndexNode, table, localDescriptorItems); - } else { - System.err.println("Unknown message type: "+messageClass); - } - - return new PSTMessage(theFile, folderIndexNode, table, localDescriptorItems); - } - - static String guessPSTObjectType(PSTFile theFile, DescriptorIndexNode folderIndexNode) - throws IOException, PSTException - { - - PSTTableBC table = new PSTTableBC(new PSTNodeInputStream(theFile, theFile.getOffsetIndexNode(folderIndexNode.dataOffsetIndexIdentifier))); - - // get the table items and look at the types we are dealing with - Set keySet = table.getItems().keySet(); - Iterator iterator = keySet.iterator(); - - while (iterator.hasNext()) { - Integer key = iterator.next(); - if (key.intValue() >= 0x0001 && - key.intValue() <= 0x0bff) - { - return "Message envelope"; - } - else if (key.intValue() >= 0x1000 && - key.intValue() <= 0x2fff) - { - return "Message content"; - } - else if (key.intValue() >= 0x3400 && - key.intValue() <= 0x35ff) - { - return "Message store"; - } - else if (key.intValue() >= 0x3600 && - key.intValue() <= 0x36ff) - { - return "Folder and address book"; - } - else if (key.intValue() >= 0x3700 && - key.intValue() <= 0x38ff) - { - return "Attachment"; - } - else if (key.intValue() >= 0x3900 && - key.intValue() <= 0x39ff) - { - return "Address book"; - } - else if (key.intValue() >= 0x3a00 && - key.intValue() <= 0x3bff) - { - return "Messaging user"; - } - else if (key.intValue() >= 0x3c00 && - key.intValue() <= 0x3cff) - { - return "Distribution list"; - } - } - return "Unknown"; - } - - /** - * the code below was taken from a random apache project - * http://www.koders.com/java/fidA9D4930E7443F69F32571905DD4CA01E4D46908C.aspx - * my bit-shifting isn't that 1337 - */ - - /** - *

The difference between the Windows epoch (1601-01-01 - * 00:00:00) and the Unix epoch (1970-01-01 00:00:00) in - * milliseconds: 11644473600000L. (Use your favorite spreadsheet - * program to verify the correctness of this value. By the way, - * did you notice that you can tell from the epochs which - * operating system is the modern one? :-))

- */ - private static final long EPOCH_DIFF = 11644473600000L; - - /** - *

Converts a Windows FILETIME into a {@link Date}. The Windows - * FILETIME structure holds a date and time associated with a - * file. The structure identifies a 64-bit integer specifying the - * number of 100-nanosecond intervals which have passed since - * January 1, 1601. This 64-bit value is split into the two double - * words stored in the structure.

- * - * @param high The higher double word of the FILETIME structure. - * @param low The lower double word of the FILETIME structure. - * @return The Windows FILETIME as a {@link Date}. - */ - protected static Date filetimeToDate(final int high, final int low) - { - final long filetime = ((long) high) << 32 | (low & 0xffffffffL); - //System.out.printf("0x%X\n", filetime); - final long ms_since_16010101 = filetime / (1000 * 10); - final long ms_since_19700101 = ms_since_16010101 - EPOCH_DIFF; - return new Date(ms_since_19700101); - } - - public static Calendar apptTimeToCalendar(int minutes) { - final long ms_since_16010101 = minutes * (60*1000L); - final long ms_since_19700101 = ms_since_16010101 - EPOCH_DIFF; - Calendar c = Calendar.getInstance(PSTTimeZone.utcTimeZone); - c.setTimeInMillis(ms_since_19700101); - return c; - } - - public static Calendar apptTimeToUTC(int minutes, PSTTimeZone tz) { - // Must convert minutes since 1/1/1601 in local time to UTC - // There's got to be a better way of doing this... - // First get a UTC calendar object that contains _local time_ - Calendar cUTC = PSTObject.apptTimeToCalendar(minutes); - if ( tz != null ) { - // Create an empty Calendar object with the required time zone - Calendar cLocal = Calendar.getInstance(tz.getSimpleTimeZone()); - cLocal.clear(); - - // Now transfer the local date/time from the UTC calendar object - // to the object that knows about the time zone... - cLocal.set(cUTC.get(Calendar.YEAR), - cUTC.get(Calendar.MONTH), - cUTC.get(Calendar.DATE), - cUTC.get(Calendar.HOUR_OF_DAY), - cUTC.get(Calendar.MINUTE), - cUTC.get(Calendar.SECOND)); - - // Get the true UTC from the local time calendar object. - // Drop any milliseconds, they won't be printed anyway! - long utcs = cLocal.getTimeInMillis() / 1000; - - // Finally, set the true UTC in the UTC calendar object - cUTC.setTimeInMillis(utcs * 1000); - } // else hope for the best! - - return cUTC; - } -} diff --git a/com/pff/PSTRecipient.java b/com/pff/PSTRecipient.java deleted file mode 100644 index 5d9bc8e..0000000 --- a/com/pff/PSTRecipient.java +++ /dev/null @@ -1,153 +0,0 @@ -/** - * 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.util.Date; -import java.util.HashMap; - -/** - * Class containing recipient information - * @author Orin Eman - * - * - */ -public class PSTRecipient { - private HashMap details; - - - public static final int MAPI_TO = 1; - public static final int MAPI_CC = 2; - public static final int MAPI_BCC = 3; - - PSTRecipient(HashMap recipientDetails) - { - details = recipientDetails; - } - - public String getDisplayName() { - return getString(0x3001); - } - - public int getRecipientType() { - return getInt(0x0c15); - } - - public String getEmailAddressType() { - return getString(0x3002); - } - - public String getEmailAddress() { - return getString(0x3003); - } - - public int getRecipientFlags() { - return getInt(0x5ffd); - } - - public int getRecipientOrder() { - return getInt(0x5fdf); - } - - public String getSmtpAddress() - { - // If the recipient address type is SMTP, - // we can simply return the recipient address. - String addressType = getEmailAddressType(); - if ( addressType!= null && - addressType.equalsIgnoreCase("smtp") ) - { - String addr = getEmailAddress(); - if ( addr != null && addr.length() != 0 ) { - return addr; - } - } - // Otherwise, we have to hope the SMTP address is - // present as the PidTagPrimarySmtpAddress property. - return getString(0x39FE); - } - - private String getString(int id) { - if ( details.containsKey(id) ) { - PSTTable7CItem item = details.get(id); - return item.getStringValue(); - } - - return ""; - } - -/* private boolean getBoolean(int id) { - if ( details.containsKey(id) ) { - PSTTable7CItem item = details.get(id); - if ( item.entryValueType == 0x000B ) - { - return (item.entryValueReference & 0xFF) == 0 ? false : true; - } - } - - return false; - } -*/ - private int getInt(int id) { - if ( details.containsKey(id) ) { - PSTTable7CItem item = details.get(id); - if ( item.entryValueType == 0x0003 ) - { - return item.entryValueReference; - } - - if ( item.entryValueType == 0x0002 ) { - short s = (short)item.entryValueReference; - return s; - } - } - - return 0; - } - -/* private Date getDate(int id) { - long lDate = 0; - - if ( details.containsKey(id) ) { - PSTTable7CItem item = details.get(id); - if ( item.entryValueType == 0x0040 ) { - int high = (int)PSTObject.convertLittleEndianBytesToLong(item.data, 4, 8); - int low = (int)PSTObject.convertLittleEndianBytesToLong(item.data, 0, 4); - - return PSTObject.filetimeToDate(high, low); - } - } - return new Date(lDate); - } -*/ -} diff --git a/com/pff/PSTRss.java b/com/pff/PSTRss.java deleted file mode 100644 index 85c830c..0000000 --- a/com/pff/PSTRss.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * 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.IOException; -import java.util.HashMap; - -/** - * Object that represents a RSS item - * @author Richard Johnson - */ -public class PSTRss extends PSTMessage { - - /** - * @param theFile - * @param descriptorIndexNode - * @throws PSTException - * @throws IOException - */ - public PSTRss(PSTFile theFile, DescriptorIndexNode descriptorIndexNode) - throws PSTException, IOException { - super(theFile, descriptorIndexNode); - } - - /** - * @param theFile - * @param folderIndexNode - * @param table - * @param localDescriptorItems - */ - public PSTRss(PSTFile theFile, DescriptorIndexNode folderIndexNode, - PSTTableBC table, - HashMap localDescriptorItems) { - super(theFile, folderIndexNode, table, localDescriptorItems); - } - - /** - * Channel - */ - public String getPostRssChannelLink() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008900, PSTFile.PSETID_PostRss)); - } - /** - * Item link - */ - public String getPostRssItemLink() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008901, PSTFile.PSETID_PostRss)); - } - /** - * Item hash Integer 32-bit signed - */ - public int getPostRssItemHash() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008902, PSTFile.PSETID_PostRss)); - } - /** - * Item GUID - */ - public String getPostRssItemGuid() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008903, PSTFile.PSETID_PostRss)); - } - /** - * Channel GUID - */ - public String getPostRssChannel() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008904, PSTFile.PSETID_PostRss)); - } - /** - * Item XML - */ - public String getPostRssItemXml() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008905, PSTFile.PSETID_PostRss)); - } - /** - * Subscription - */ - public String getPostRssSubscription() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008906, PSTFile.PSETID_PostRss)); - } - - public String toString() { - return - "Channel ASCII or Unicode string values: "+ getPostRssChannelLink() + "\n" + - "Item link ASCII or Unicode string values: "+ getPostRssItemLink() + "\n" + - "Item hash Integer 32-bit signed: "+ getPostRssItemHash() + "\n" + - "Item GUID ASCII or Unicode string values: "+ getPostRssItemGuid() + "\n" + - "Channel GUID ASCII or Unicode string values: "+ getPostRssChannel() + "\n" + - "Item XML ASCII or Unicode string values: "+ getPostRssItemXml() + "\n" + - "Subscription ASCII or Unicode string values: "+ getPostRssSubscription(); - } -} diff --git a/com/pff/PSTTable.java b/com/pff/PSTTable.java deleted file mode 100644 index 1e27667..0000000 --- a/com/pff/PSTTable.java +++ /dev/null @@ -1,269 +0,0 @@ -/** - * 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.IOException; -import java.util.HashMap; - - -/** - * The PST Table is the workhorse of the whole system. - * It allows for an item to be read and broken down into the individual properties that it consists of. - * For most PST Objects, it appears that only 7c and bc table types are used. - * @author Richard Johnson - */ -class PSTTable { - - protected String tableType; - protected byte tableTypeByte; - protected int hidUserRoot; - - protected Long[] arrayBlocks = null; - - // info from the b5 header - protected int sizeOfItemKey; - protected int sizeOfItemValue; - protected int hidRoot; - protected int numberOfKeys = 0; - protected int numberOfIndexLevels = 0; - - private PSTNodeInputStream in; - - //private int[][] rgbiAlloc = null; - //private byte[] data = null; - private HashMap subNodeDescriptorItems = null; - - protected String description = ""; - - protected PSTTable(PSTNodeInputStream in, HashMap subNodeDescriptorItems) - throws PSTException, IOException - { - this.subNodeDescriptorItems = subNodeDescriptorItems; - this.in = in; - - arrayBlocks = in.getBlockOffsets(); - - // the next two bytes should be the table type (bSig) - // 0xEC is HN (Heap-on-Node) - in.seek(0); - byte[] headdata = new byte[4]; - in.read(headdata); - if ((int)headdata[2] != 0xffffffec) { - //System.out.println(in.isEncrypted()); - PSTObject.decode(headdata); - PSTObject.printHexFormatted(headdata, true); - throw new PSTException("Unable to parse table, bad table type..."); - } - - tableTypeByte = headdata[3]; - switch ((int)tableTypeByte) { // bClientSig - case 0x7c: // Table Context (TC/HN) - tableType = "7c"; - break; -// case 0x9c: -// tableType = "9c"; -// valid = true; -// break; -// case 0xa5: -// tableType = "a5"; -// valid = true; -// break; -// case 0xac: -// tableType = "ac"; -// valid = true; -// break; -// case 0xFFFFFFb5: // BTree-on-Heap (BTH) -// tableType = "b5"; -// valid = true; -// break; - case 0xffffffbc: - tableType = "bc"; // Property Context (PC/BTH) - break; - default: - throw new PSTException("Unable to parse table, bad table type. Unknown identifier: 0x"+Long.toHexString(headdata[3])); - } - - - hidUserRoot = (int)in.seekAndReadLong(4, 4); // hidUserRoot -/* - System.out.printf("Table %s: hidUserRoot 0x%08X\n", tableType, hidUserRoot); -/**/ - - - // all tables should have a BTHHEADER at hnid == 0x20 - NodeInfo headerNodeInfo = getNodeInfo(0x20); - headerNodeInfo.in.seek(headerNodeInfo.startOffset); - int headerByte = headerNodeInfo.in.read() &0xFF; - if ( headerByte != 0xb5 ) { - headerNodeInfo.in.seek(headerNodeInfo.startOffset); - headerByte = headerNodeInfo.in.read() &0xFF; - headerNodeInfo.in.seek(headerNodeInfo.startOffset); - byte[] tmp = new byte[1024]; - headerNodeInfo.in.read(tmp); - PSTObject.printHexFormatted(tmp, true); - //System.out.println(PSTObject.compEnc[headerByte]); - throw new PSTException("Unable to parse table, can't find BTHHEADER header information: "+headerByte); - } - - sizeOfItemKey = (int)headerNodeInfo.in.read() & 0xFF; // Size of key in key table - sizeOfItemValue = (int)headerNodeInfo.in.read() & 0xFF; // Size of value in key table - - numberOfIndexLevels = (int)headerNodeInfo.in.read() & 0xFF; - if ( numberOfIndexLevels != 0 ) { - // System.out.println(this.tableType); - // System.out.printf("Table with %d index levels\n", numberOfIndexLevels); - } - //hidRoot = (int)PSTObject.convertLittleEndianBytesToLong(nodeInfo, 4, 8); // hidRoot - hidRoot = (int)headerNodeInfo.seekAndReadLong(4, 4); - //System.out.println(hidRoot); - //System.exit(0); -/* - System.out.printf("Table %s: hidRoot 0x%08X\n", tableType, hidRoot); -/**/ - description += "Table ("+tableType+")\n"+ - "hidUserRoot: "+hidUserRoot+" - 0x"+Long.toHexString(hidUserRoot)+"\n"+ - "Size Of Keys: "+sizeOfItemKey+" - 0x"+Long.toHexString(sizeOfItemKey)+"\n"+ - "Size Of Values: "+sizeOfItemValue+" - 0x"+Long.toHexString(sizeOfItemValue)+"\n"+ - "hidRoot: "+hidRoot+" - 0x"+Long.toHexString(hidRoot)+"\n"; - } - - - protected void releaseRawData() { - subNodeDescriptorItems = null; - } - - - /** - * get the number of items stored in this table. - * @return - */ - public int getRowCount() { - return this.numberOfKeys; - } - - class NodeInfo - { - int startOffset; - int endOffset; - //byte[] data; - PSTNodeInputStream in; - - NodeInfo(int start, int end, PSTNodeInputStream in) - throws PSTException - { - if (start > end) - throw new PSTException(String.format("Invalid NodeInfo parameters: start %1$d is greater than end %2$d", start, end)); - startOffset = start; - endOffset = end; - this.in = in; - //this.data = data; - } - - int length() { - return endOffset - startOffset; - } - - long seekAndReadLong(long offset, int length) - throws IOException, PSTException - { - return this.in.seekAndReadLong(startOffset+offset, length); - } - } - - protected NodeInfo getNodeInfo(int hnid) - throws PSTException, IOException - { - - // Zero-length node? - if ( hnid == 0 ) { - return new NodeInfo(0, 0, this.in); - } - - // Is it a subnode ID? - if ( subNodeDescriptorItems != null && - subNodeDescriptorItems.containsKey(hnid) ) - { - PSTDescriptorItem item = subNodeDescriptorItems.get(hnid); - //byte[] data; - NodeInfo subNodeInfo = null; - - try { - //data = item.getData(); - PSTNodeInputStream subNodeIn = new PSTNodeInputStream(in.getPSTFile(), item); - subNodeInfo = new NodeInfo(0, (int)subNodeIn.length(), subNodeIn); - } catch (IOException e) { - throw new PSTException(String.format("IOException reading subNode: 0x%08X", hnid)); - } - - //return new NodeInfo(0, data.length, data); - return subNodeInfo; - } - - if ( (hnid & 0x1F) != 0 ) { - // Some kind of external node - return null; - } - - int whichBlock = (hnid >>> 16); - if ( whichBlock > this.arrayBlocks.length ) { - // Block doesn't exist! - String err = String.format("getNodeInfo: block doesn't exist! hnid = 0x%08X\n", hnid); - err += String.format("getNodeInfo: block doesn't exist! whichBlock = 0x%08X\n", whichBlock); - err += "\n"+(this.arrayBlocks.length); - throw new PSTException(err); - //return null; - } - - // A normal node in a local heap - int index = (hnid & 0xFFFF) >> 5; - int blockOffset = 0; - if (whichBlock > 0) { - blockOffset = arrayBlocks[whichBlock-1].intValue(); - } - // Get offset of HN page map - int iHeapNodePageMap = (int)in.seekAndReadLong(blockOffset, 2) + blockOffset; - int cAlloc = (int)in.seekAndReadLong(iHeapNodePageMap, 2); - if ( index >= cAlloc+1 ) { - throw new PSTException(String.format("getNodeInfo: node index doesn't exist! nid = 0x%08X\n", hnid)); - //return null; - } - iHeapNodePageMap += (2 * index)+2; - int start = (int)in.seekAndReadLong(iHeapNodePageMap, 2) + blockOffset; - int end = (int)in.seekAndReadLong(iHeapNodePageMap + 2, 2) + blockOffset; - - NodeInfo out = new NodeInfo(start, end, in); - return out; - } - -} diff --git a/com/pff/PSTTable7C.java b/com/pff/PSTTable7C.java deleted file mode 100644 index e092ebf..0000000 --- a/com/pff/PSTTable7C.java +++ /dev/null @@ -1,421 +0,0 @@ -/** - * 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.UnsupportedEncodingException; -/**/ -import java.util.*; -import java.io.*; - -/** - * Specific functions for the 7c table type ("Table Context"). - * This is used for attachments. - * @author Richard Johnson - */ -class PSTTable7C extends PSTTable { - - private final int BLOCK_SIZE = 8176; - - private List> items = null; - private int numberOfDataSets = 0; - private int cCols = 0; - private int TCI_bm = 0; - private NodeInfo rowNodeInfo = null; - private int TCI_1b = 0; - private int overrideCol = -1; - - protected PSTTable7C(PSTNodeInputStream in, HashMap subNodeDescriptorItems) - throws PSTException, java.io.IOException - { - this(in, subNodeDescriptorItems, -1); - } - protected PSTTable7C(PSTNodeInputStream in, HashMap subNodeDescriptorItems, int entityToExtract) - throws PSTException, java.io.IOException - { - super(in, subNodeDescriptorItems); - - if (tableTypeByte != 0x7c) - { - //System.out.println(Long.toHexString(this.tableTypeByte)); - throw new PSTException("unable to create PSTTable7C, table does not appear to be a 7c!"); - } - - // TCINFO header is in the hidUserRoot node - //byte[] tcHeaderNode = getNodeInfo(hidUserRoot); - NodeInfo tcHeaderNode = getNodeInfo(hidUserRoot); - int offset = 0; - - // get the TCINFO header information - //int cCols = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+1, offset+2); - cCols = (int)tcHeaderNode.seekAndReadLong(offset+1, 1); - @SuppressWarnings("unused") - //int TCI_4b = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+2, offset+4); - int TCI_4b = (int)tcHeaderNode.seekAndReadLong(offset+2, 2); - @SuppressWarnings("unused") - //int TCI_2b = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+4, offset+6); - int TCI_2b = (int)tcHeaderNode.seekAndReadLong(offset+4, 2); - //int TCI_1b = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+6, offset+8); - TCI_1b = (int)tcHeaderNode.seekAndReadLong(offset+6, 2); - //int TCI_bm = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+8, offset+10); - TCI_bm = (int)tcHeaderNode.seekAndReadLong(offset+8, 2); - //int hidRowIndex = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+10, offset+14); - int hidRowIndex = (int)tcHeaderNode.seekAndReadLong(offset+10, 4); - //int hnidRows = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+14, offset+18);// was 18 - int hnidRows = (int)tcHeaderNode.seekAndReadLong(offset+14, 4); - // 18..22 hidIndex - deprecated - - // 22... column descriptors - offset += 22; - if ( cCols != 0 ) { - columnDescriptors = new ColumnDescriptor[cCols]; - - for ( int col = 0; col < cCols; ++col ) { - //columnDescriptors[col] = new ColumnDescriptor(tcHeaderNode, offset); - columnDescriptors[col] = new ColumnDescriptor(tcHeaderNode, offset); - //System.out.println("iBit: "+col+" " +columnDescriptors[col].iBit); - if (columnDescriptors[col].id == entityToExtract) { - overrideCol = col; - } - offset += 8; - } - } - - // if we are asking for a specific column, only get that! - if (overrideCol > -1) { - cCols = overrideCol +1; - } - - // Read the key table -/* System.out.printf("Key table:\n"); /**/ - keyMap = new HashMap(); - //byte[] keyTableInfo = getNodeInfo(hidRoot); - NodeInfo keyTableInfo = getNodeInfo(hidRoot); - numberOfKeys = keyTableInfo.length() / (sizeOfItemKey+sizeOfItemValue); - offset = 0; - for (int x = 0; x < numberOfKeys; x++) { - int Context = (int)keyTableInfo.seekAndReadLong(offset, sizeOfItemKey); - offset += sizeOfItemKey; - int RowIndex = (int)keyTableInfo.seekAndReadLong(offset, sizeOfItemValue); - offset += sizeOfItemValue; - keyMap.put(Context, RowIndex); - } - - // Read the Row Matrix - rowNodeInfo = getNodeInfo(hnidRows); - //numberOfDataSets = (rowNodeInfo.endOffset - rowNodeInfo.startOffset) / TCI_bm; - - description += - "Number of keys: "+numberOfKeys+"\n"+ - "Number of columns: "+cCols+"\n"+ - "Row Size: "+TCI_bm+"\n"+ - "hidRowIndex: "+hidRowIndex+"\n"+ - "hnidRows: "+hnidRows+"\n"; - - int numberOfBlocks = rowNodeInfo.length() / BLOCK_SIZE; - int numberOfRowsPerBlock = BLOCK_SIZE / TCI_bm; - @SuppressWarnings("unused") - int blockPadding = BLOCK_SIZE - (numberOfRowsPerBlock * TCI_bm); - numberOfDataSets = (numberOfBlocks * numberOfRowsPerBlock) + ((rowNodeInfo.length() % BLOCK_SIZE) / TCI_bm); - } - - /** - * get all the items parsed out of this table. - * @return - */ - List> getItems() - throws PSTException, IOException - { - if ( items == null ) { - items = getItems(-1, -1); - } - return items; - } - - List> getItems(int startAtRecord, int numberOfRecordsToReturn) - throws PSTException, IOException - { - List> itemList = new ArrayList>(); - - // okay, work out the number of records we have - int numberOfBlocks = rowNodeInfo.length() / BLOCK_SIZE; - int numberOfRowsPerBlock = BLOCK_SIZE / TCI_bm; - int blockPadding = BLOCK_SIZE - (numberOfRowsPerBlock * TCI_bm); - numberOfDataSets = (numberOfBlocks * numberOfRowsPerBlock) + ((rowNodeInfo.length() % BLOCK_SIZE) / TCI_bm); - - if (startAtRecord == -1) { - numberOfRecordsToReturn = numberOfDataSets; - startAtRecord = 0; - } - - // repeat the reading process for every dataset - int currentValueArrayStart = - ((startAtRecord / numberOfRowsPerBlock) * BLOCK_SIZE) + - ((startAtRecord % numberOfRowsPerBlock) * TCI_bm); - - if (numberOfRecordsToReturn > this.getRowCount() - startAtRecord) { - numberOfRecordsToReturn = this.getRowCount() - startAtRecord; - } - - int dataSetNumber = 0; - //while ( currentValueArrayStart + ((cCols+7)/8) + TCI_1b <= rowNodeInfo.length()) - for (int rowCounter = 0; rowCounter < numberOfRecordsToReturn; rowCounter++) - { - HashMap currentItem = new HashMap(); - // add on some padding for block boundries? - if (rowNodeInfo.in.getPSTFile().getPSTFileType() == PSTFile.PST_TYPE_ANSI) { - if (currentValueArrayStart >= BLOCK_SIZE) { - currentValueArrayStart = currentValueArrayStart + (4) * (currentValueArrayStart / BLOCK_SIZE); - } - if (rowNodeInfo.startOffset+ currentValueArrayStart + TCI_1b > rowNodeInfo.in.length()) { - continue; - } - } else { - if ((currentValueArrayStart % BLOCK_SIZE) > BLOCK_SIZE - TCI_bm) { - // adjust! - //currentValueArrayStart += 8176 - (currentValueArrayStart % 8176); - currentValueArrayStart += blockPadding; - if (currentValueArrayStart + TCI_bm < rowNodeInfo.length()) { - continue; - } - } - } - byte[] bitmap = new byte[(cCols+7)/8]; - //System.arraycopy(rowNodeInfo, currentValueArrayStart+TCI_1b, bitmap, 0, bitmap.length); - rowNodeInfo.in.seek(rowNodeInfo.startOffset+ currentValueArrayStart + TCI_1b); - rowNodeInfo.in.read(bitmap); - - //int id = (int)PSTObject.convertLittleEndianBytesToLong(rowNodeInfo, currentValueArrayStart, currentValueArrayStart+4); - int id = (int)rowNodeInfo.seekAndReadLong(currentValueArrayStart, 4); - - // Put into the item map as PidTagLtpRowId (0x67F2) - PSTTable7CItem item = new PSTTable7CItem(); - item.itemIndex = -1; - item.entryValueType = 3; - item.entryType = 0x67F2; - item.entryValueReference = id; - item.isExternalValueReference = true; - currentItem.put(item.entryType, item); - - int col = 0; - if (overrideCol > -1) { - col = overrideCol; - } - for ( ; col < cCols; ++col ) - { - // Does this column exist for this row? - int bitIndex = columnDescriptors[col].iBit / 8; - int bit = columnDescriptors[col].iBit % 8; - if ( bitIndex >= bitmap.length || (bitmap[bitIndex] & (1< 0 ) { - System.out.println("here"); - System.out.println(rowNodeInfo.length()); - PSTObject.printHexFormatted(rowNodeInfo, true); - System.exit(0); - } - */ - - //item.entryValueReference = (int)PSTObject.convertLittleEndianBytesToLong(rowNodeInfo, currentValueArrayStart+columnDescriptors[col].ibData, currentValueArrayStart+columnDescriptors[col].ibData+4); - item.entryValueReference = (int)rowNodeInfo.seekAndReadLong(currentValueArrayStart+columnDescriptors[col].ibData, 4); - if ( columnDescriptors[col].type == 0x0003 || - columnDescriptors[col].type == 0x0004 || - columnDescriptors[col].type == 0x000A ) { - // True 32bit data - item.isExternalValueReference = true; -/* System.out.printf("\tInteger32: %s %d\n", - PSTFile.getPropertyDescription(item.entryType, item.entryValueType), - item.entryValueReference); /**/ - break; - } - - // Variable length data so it's an hnid - if ( (item.entryValueReference & 0x1F) != 0 ) { - // Some kind of external reference... - item.isExternalValueReference = true; -/* System.out.printf("\tOther: %s 0x%08X\n", - PSTFile.getPropertyDescription(item.entryType, item.entryValueType), item.entryValueReference); /**/ - break; - } - - if ( item.entryValueReference == 0 ) { -/* System.out.printf("\tOther: %s 0 bytes\n", - PSTFile.getPropertyDescription(item.entryType, item.entryValueType)); /**/ - item.data = new byte[0]; - break; - } else { - NodeInfo entryInfo = getNodeInfo(item.entryValueReference); - item.data = new byte[entryInfo.length()]; - //System.arraycopy(entryInfo, 0, item.data, 0, item.data.length); - entryInfo.in.seek(entryInfo.startOffset); - entryInfo.in.read(item.data); - } -/* - if ( item.entryValueType != 0x001F ) { - System.out.printf("\tOther: %s %d bytes\n", - PSTFile.getPropertyDescription(item.entryType, item.entryValueType), - item.data.length); - } else { - try { - String s = new String(item.data, "UTF-16LE"); - System.out.printf("\tString: %s \"%s\"\n", - PSTFile.getPropertyDescription(item.entryType, item.entryValueType), - s); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - } -/**/ - break; - } - - currentItem.put(item.entryType, item); - - //description += item.toString()+"\n\n"; - } - itemList.add(dataSetNumber, currentItem); - dataSetNumber++; - currentValueArrayStart += TCI_bm; - } - -// System.out.println(description); - - return itemList; - } - - class ColumnDescriptor { - ColumnDescriptor(NodeInfo nodeInfo, int offset) - throws PSTException, IOException - { - //type = (int)(PSTObject.convertLittleEndianBytesToLong(data, offset, offset+2) & 0xFFFF); - type = ((int)nodeInfo.seekAndReadLong(offset, 2) & 0xFFFF); - //id = (int)(PSTObject.convertLittleEndianBytesToLong(data, offset+2, offset+4) & 0xFFFF); - id = (int)(nodeInfo.seekAndReadLong(offset+2, 2) & 0xFFFF); - //ibData = (int)(PSTObject.convertLittleEndianBytesToLong(data, offset+4, offset+6) & 0xFFFF); - ibData = (int)(nodeInfo.seekAndReadLong(offset+4, 2) & 0xFFFF); - //cbData = (int)data[offset+6] & 0xFF; - cbData = nodeInfo.in.read() & 0xFF; - //iBit = (int)data[offset+7] & 0xFF; - iBit = nodeInfo.in.read() & 0xFF; - } - - int type; - int id; - int ibData; - int cbData; - int iBit; - } - - @Override - public int getRowCount() { - return this.numberOfDataSets; - } -/* Not used... - public HashMap getItem(int itemNumber) { - if ( items == null || itemNumber >= items.size() ) { - return null; - } - - return items.get(itemNumber); - } -/**/ - @Override - public String toString() { - return this.description; - } - - public String getItemsString() { - if ( items == null ) { - return ""; - } - - return items.toString(); - } - - ColumnDescriptor[] columnDescriptors = null; - HashMap keyMap = null; -} diff --git a/com/pff/PSTTable7CItem.java b/com/pff/PSTTable7CItem.java deleted file mode 100644 index 16b9877..0000000 --- a/com/pff/PSTTable7CItem.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * 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; - -/** - * Items found in the 7c tables - * @author Richard Johnson - */ -class PSTTable7CItem extends PSTTableItem -{ - - public String toString() { - return "7c Table Item: " + super.toString() + "\n"; - } -} diff --git a/com/pff/PSTTableBC.java b/com/pff/PSTTableBC.java deleted file mode 100644 index f681248..0000000 --- a/com/pff/PSTTableBC.java +++ /dev/null @@ -1,204 +0,0 @@ -/** - * 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.util.HashMap; - - -/** - * The BC Table type. (Property Context) - * Used by pretty much everything. - * @author Richard Johnson - */ -class PSTTableBC extends PSTTable { - - private HashMap items = new HashMap(); - - private StringBuilder descBuffer = new StringBuilder(); - private boolean isDescNotYetInitiated = false; - - PSTTableBC(PSTNodeInputStream in) - throws PSTException, java.io.IOException - { - super(in, new HashMap()); - //data = null; // No direct access to data! - - - if (tableTypeByte != 0xffffffbc) - { - //System.out.println(Long.toHexString(this.tableTypeByte)); - throw new PSTException("unable to create PSTTableBC, table does not appear to be a bc!"); - } - - // go through each of the entries. - //byte[] keyTableInfo = getNodeInfo(hidRoot); - NodeInfo keyTableInfoNodeInfo = getNodeInfo(hidRoot); - byte[] keyTableInfo = new byte[keyTableInfoNodeInfo.length()]; - keyTableInfoNodeInfo.in.seek(keyTableInfoNodeInfo.startOffset); - keyTableInfoNodeInfo.in.read(keyTableInfo); - - //PSTObject.printHexFormatted(keyTableInfo, true); - //System.out.println(in.length()); - //System.exit(0); - numberOfKeys = keyTableInfo.length / (sizeOfItemKey+sizeOfItemValue); - - descBuffer.append("Number of entries: " + numberOfKeys + "\n"); - - // Read the key table - int offset = 0; - for (int x = 0; x < numberOfKeys; x++) { - - PSTTableBCItem item = new PSTTableBCItem(); - item.itemIndex = x; - item.entryType =(int)PSTObject.convertLittleEndianBytesToLong(keyTableInfo, offset+0, offset+2); - //item.entryType =(int)in.seekAndReadLong(offset, 2); - item.entryValueType = (int)PSTObject.convertLittleEndianBytesToLong(keyTableInfo, offset+2, offset+4); - //item.entryValueType = (int)in.seekAndReadLong(offset+2, 2); - item.entryValueReference = (int)PSTObject.convertLittleEndianBytesToLong(keyTableInfo, offset+4, offset+8); - //item.entryValueReference = (int)in.seekAndReadLong(offset+4, 4); - - // Data is in entryValueReference for all types <= 4 bytes long - switch ( item.entryValueType ) { - - case 0x0002: // 16bit integer - item.entryValueReference &= 0xFFFF; - case 0x0003: // 32bit integer - case 0x000A: // 32bit error code -/* - System.out.printf("Integer%s: 0x%04X:%04X, %d\n", - (item.entryValueType == 0x0002) ? "16" : "32", - item.entryType, item.entryValueType, - item.entryValueReference); -/**/ - case 0x0001: // Place-holder - case 0x0004: // 32bit floating - item.isExternalValueReference = true; - break; - - case 0x000b: // Boolean - a single byte - item.entryValueReference &= 0xFF; -/* - System.out.printf("boolean: 0x%04X:%04X, %s\n", - item.entryType, item.entryValueType, - (item.entryValueReference == 0) ? "false" : "true"); -/**/ - item.isExternalValueReference = true; - break; - - case 0x000D: - default: - // Is it in the local heap? - item.isExternalValueReference = true; // Assume not - //System.out.println(item.entryValueReference); - //byte[] nodeInfo = getNodeInfo(item.entryValueReference); - NodeInfo nodeInfoNodeInfo = getNodeInfo(item.entryValueReference); - if ( nodeInfoNodeInfo == null ) { - // It's an external reference that we don't deal with here. -/* - System.out.printf("%s: %shid 0x%08X\n", - (item.entryValueType == 0x1f || item.entryValueType == 0x1e) ? "String" : "Other", - PSTFile.getPropertyDescription(item.entryType, item.entryValueType), - item.entryValueReference); -/**/ - } else { - // Make a copy of the data - //item.data = new byte[nodeInfo.endOffset-nodeInfo.startOffset]; - byte[] nodeInfo = new byte[nodeInfoNodeInfo.length()]; - nodeInfoNodeInfo.in.seek(nodeInfoNodeInfo.startOffset); - nodeInfoNodeInfo.in.read(nodeInfo); - item.data = nodeInfo; // should be new array, so just use it - //System.arraycopy(nodeInfo.data, nodeInfo.startOffset, item.data, 0, item.data.length); - item.isExternalValueReference = false; -/* - if ( item.entryValueType == 0x1f || - item.entryValueType == 0x1e ) - { - try { -// if ( item.entryType == 0x0037 ) - { - String temp = new String(item.data, item.entryValueType == 0x1E ? "UTF8" : "UTF-16LE"); - System.out.printf("String: 0x%04X:%04X, \"%s\"\n", - item.entryType, item.entryValueType, temp); - } - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - } - else - { - - System.out.printf("Other: 0x%04X:%04X, %d bytes\n", - item.entryType, item.entryValueType, item.data.length); - - } -/**/ - } - break; - } - - offset = offset + 8; - - items.put(item.entryType, item); - // description += item.toString()+"\n\n"; - - } - - releaseRawData(); - } - - - /** - * get the items parsed out of this table. - * @return - */ - public HashMap getItems() { - return this.items; - } - - public String toString() { - - if (isDescNotYetInitiated) { - isDescNotYetInitiated=false; - - for (Integer curItem:items.keySet()) { - descBuffer.append(items.get(curItem).toString() + "\n\n"); - } -// description += item.toString()+"\n\n"; - } - - - return this.description + descBuffer.toString(); - } -} - diff --git a/com/pff/PSTTableBCItem.java b/com/pff/PSTTableBCItem.java deleted file mode 100644 index 4f7b330..0000000 --- a/com/pff/PSTTableBCItem.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * 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; - -/** - * Items within the BC Table - * @author Richard Johnson - */ -class PSTTableBCItem extends PSTTableItem -{ - - public String toString() { - return "Table Item: "+super.toString() + "\n"; - } -} diff --git a/com/pff/PSTTableItem.java b/com/pff/PSTTableItem.java deleted file mode 100644 index fe5fd1a..0000000 --- a/com/pff/PSTTableItem.java +++ /dev/null @@ -1,195 +0,0 @@ -/** - * 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.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.SimpleTimeZone; - -/** - * Generic table item. - * Provides some basic string functions - * @author Richard Johnson - */ -class PSTTableItem { - - public static final int VALUE_TYPE_PT_UNICODE = 0x1f; - public static final int VALUE_TYPE_PT_STRING8 = 0x1e; - public static final int VALUE_TYPE_PT_BIN = 0x102; - - public int itemIndex = 0; - public int entryType = 0; - public int entryValueType = 0; - public int entryValueReference = 0; - public byte[] data = new byte[0]; - public boolean isExternalValueReference = false; - - public long getLongValue() { - if ( this.data.length > 0 ) { - return PSTObject.convertLittleEndianBytesToLong(data); - } - return -1; - } - - public String getStringValue() { - return getStringValue(entryValueType); - } - - /** - * get a string value of the data - * @param forceString if true, you won't get the hex representation of the data - * @return - */ - public String getStringValue(int stringType) { - - if (stringType == VALUE_TYPE_PT_UNICODE) { - // we are a nice little-endian unicode string. - try { - if (isExternalValueReference ) { - return "External string reference!"; - } - return new String(data, "UTF-16LE"); - } catch (UnsupportedEncodingException e) { - - System.err.println("Error decoding string: " + data.toString()); - return ""; - } - } - - if (stringType == VALUE_TYPE_PT_STRING8 ) { - //System.out.println("Warning! decoding string8 without charset: "+this.entryType + " - "+ Integer.toHexString(this.entryType)); - return new String(data, Charset.forName("UTF-8")); - } - - StringBuffer outputBuffer = new StringBuffer(); -/* - if ( stringType == VALUE_TYPE_PT_BIN) { - int theChar; - for (int x = 0; x < data.length; x++) { - theChar = data[x] & 0xFF; - outputBuffer.append((char)theChar); - } - } - else -/**/ - { - // we are not a normal string, give a hexish sort of output - StringBuffer hexOut = new StringBuffer(); - for (int y = 0; y < data.length; y++) { - int valueChar = (int)data[y] & 0xff; - if (Character.isLetterOrDigit((char)valueChar)) { - outputBuffer.append((char)valueChar); - outputBuffer.append(" "); - } - else - { - outputBuffer.append(". "); - } - String hexValue = Long.toHexString(valueChar); - hexOut.append(hexValue); - hexOut.append(" "); - if (hexValue.length() > 1) { - outputBuffer.append(" "); - } - } - outputBuffer.append("\n"); - outputBuffer.append(" "); - outputBuffer.append(hexOut); - } - - return new String(outputBuffer); - } - - public String toString() { - String ret = PSTFile.getPropertyDescription(entryType, entryValueType); - - if ( entryValueType == 0x000B ) - { - return ret + (entryValueReference == 0 ? "false" : "true"); - } - - if ( isExternalValueReference ) { - // Either a true external reference, or entryValueReference contains the actual data - return ret + String.format("0x%08X (%d)", entryValueReference, entryValueReference); - } - - if ( entryValueType == 0x0005 || - entryValueType == 0x0014 ) { - // 64bit data - if ( data == null ) { - return ret + "no data"; - } - if ( data.length == 8 ) { - long l = PSTObject.convertLittleEndianBytesToLong(data, 0, 8); - return String.format("%s0x%016X (%d)", ret, l, l); - } else { - return String.format("%s invalid data length: %d", ret, data.length); - } - } - - if ( entryValueType == 0x0040 ) { - // It's a date... - int high = (int)PSTObject.convertLittleEndianBytesToLong(data, 4, 8); - int low = (int)PSTObject.convertLittleEndianBytesToLong(data, 0, 4); - - Date d = PSTObject.filetimeToDate(high, low); - dateFormatter.setTimeZone(utcTimeZone); - return ret + dateFormatter.format(d); - } - - if ( entryValueType == 0x001F ) { - // Unicode string - String s; - try { - s = new String(data, "UTF-16LE"); - } catch (UnsupportedEncodingException e) { - System.err.println("Error decoding string: " + data.toString()); - s = ""; - } - - if ( s.length() >= 2 && s.charAt(0) == 0x0001 ) { - return String.format("%s [%04X][%04X]%s", ret, (short)s.charAt(0), (short)s.charAt(1), s.substring(2)); - } - - return ret + s; - } - - return ret + getStringValue(); - } - - private SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); - private static SimpleTimeZone utcTimeZone = new SimpleTimeZone(0, "UTC"); -} diff --git a/com/pff/PSTTask.java b/com/pff/PSTTask.java deleted file mode 100644 index c58888b..0000000 --- a/com/pff/PSTTask.java +++ /dev/null @@ -1,191 +0,0 @@ -/** - * 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.IOException; -import java.util.HashMap; -import java.util.Date; - -/** - * Object that represents Task items - * @author Richard Johnson - */ -public class PSTTask extends PSTMessage { - - /** - * @param theFile - * @param descriptorIndexNode - * @throws PSTException - * @throws IOException - */ - public PSTTask(PSTFile theFile, DescriptorIndexNode descriptorIndexNode) - throws PSTException, IOException { - super(theFile, descriptorIndexNode); - } - - /** - * @param theFile - * @param folderIndexNode - * @param table - * @param localDescriptorItems - */ - public PSTTask(PSTFile theFile, DescriptorIndexNode folderIndexNode, - PSTTableBC table, - HashMap localDescriptorItems) { - super(theFile, folderIndexNode, table, localDescriptorItems); - } - - /** - * Status Integer 32-bit signed 0x0 => Not started - */ - public int getTaskStatus() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008101, PSTFile.PSETID_Task)); - } - /** - * Percent Complete Floating point double precision (64-bit) - */ - public double getPercentComplete() { - return getDoubleItem(pstFile.getNameToIdMapItem(0x00008102, PSTFile.PSETID_Task)); - } - /** - * Is team task Boolean - */ - public boolean isTeamTask() { - return getBooleanItem(pstFile.getNameToIdMapItem(0x00008103, PSTFile.PSETID_Task)); - } - - /** - * Date completed Filetime - */ - public Date getTaskDateCompleted() { - return getDateItem(pstFile.getNameToIdMapItem(0x0000810f, PSTFile.PSETID_Task)); - } - /** - * Actual effort in minutes Integer 32-bit signed - */ - public int getTaskActualEffort() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008110, PSTFile.PSETID_Task)); - } - /** - * Total effort in minutes Integer 32-bit signed - */ - public int getTaskEstimatedEffort() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008111, PSTFile.PSETID_Task)); - } - /** - * Task version Integer 32-bit signed FTK: Access count - */ - public int getTaskVersion() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008112, PSTFile.PSETID_Task)); - } - /** - * Complete Boolean - */ - public boolean isTaskComplete() { - return getBooleanItem(pstFile.getNameToIdMapItem(0x0000811c, PSTFile.PSETID_Task)); - } - /** - * Owner ASCII or Unicode string - */ - public String getTaskOwner() { - return getStringItem(pstFile.getNameToIdMapItem(0x0000811f, PSTFile.PSETID_Task)); - } - /** - * Delegator ASCII or Unicode string - */ - public String getTaskAssigner() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008121, PSTFile.PSETID_Task)); - } - /** - * Unknown ASCII or Unicode string - */ - public String getTaskLastUser() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008122, PSTFile.PSETID_Task)); - } - /** - * Ordinal Integer 32-bit signed - */ - public int getTaskOrdinal() { - return this.getIntItem(pstFile.getNameToIdMapItem(0x00008123, PSTFile.PSETID_Task)); - } - /** - * Is recurring Boolean - */ - public boolean isTaskFRecurring() { - return getBooleanItem(pstFile.getNameToIdMapItem(0x00008126, PSTFile.PSETID_Task)); - } - /** - * Role ASCII or Unicode string - */ - public String getTaskRole() { - return getStringItem(pstFile.getNameToIdMapItem(0x00008127, PSTFile.PSETID_Task)); - } - /** - * Ownership Integer 32-bit signed - */ - public int getTaskOwnership() { - return getIntItem(pstFile.getNameToIdMapItem(0x00008129, PSTFile.PSETID_Task)); - } - /** - * Delegation State - */ - public int getAcceptanceState() { - return getIntItem(pstFile.getNameToIdMapItem(0x0000812a, PSTFile.PSETID_Task)); - } - - public String toString() { - return - "Status Integer 32-bit signed 0x0 => Not started [TODO]: "+getTaskStatus()+"\n"+ - "Percent Complete Floating point double precision (64-bit): "+getPercentComplete()+"\n"+ - "Is team task Boolean: "+isTeamTask()+"\n"+ - "Start date Filetime: "+getTaskStartDate()+"\n"+ - "Due date Filetime: "+getTaskDueDate()+"\n"+ - "Date completed Filetime: "+getTaskDateCompleted()+"\n"+ - "Actual effort in minutes Integer 32-bit signed: "+getTaskActualEffort()+"\n"+ - "Total effort in minutes Integer 32-bit signed: "+getTaskEstimatedEffort()+"\n"+ - "Task version Integer 32-bit signed FTK: Access count: "+getTaskVersion()+"\n"+ - "Complete Boolean: "+isTaskComplete()+"\n"+ - "Owner ASCII or Unicode string: "+getTaskOwner()+"\n"+ - "Delegator ASCII or Unicode string: "+getTaskAssigner()+"\n"+ - "Unknown ASCII or Unicode string: "+getTaskLastUser()+"\n"+ - "Ordinal Integer 32-bit signed: "+getTaskOrdinal()+"\n"+ - "Is recurring Boolean: "+isTaskFRecurring()+"\n"+ - "Role ASCII or Unicode string: "+getTaskRole()+"\n"+ - "Ownership Integer 32-bit signed: "+getTaskOwnership()+"\n"+ - "Delegation State: "+getAcceptanceState(); - - - - } -} diff --git a/com/pff/PSTTimeZone.java b/com/pff/PSTTimeZone.java deleted file mode 100644 index 063d2ca..0000000 --- a/com/pff/PSTTimeZone.java +++ /dev/null @@ -1,266 +0,0 @@ -/** - * 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.util.Calendar; -import java.util.SimpleTimeZone; - -/** - * Class containing time zone information - * @author Orin Eman - * - * - */ - -public class PSTTimeZone { - PSTTimeZone(byte [] timeZoneData) { - this.rule = null; - name = ""; - - try { - int headerLen = (int)PSTObject.convertLittleEndianBytesToLong(timeZoneData, 2, 4); - int nameLen = 2*(int)PSTObject.convertLittleEndianBytesToLong(timeZoneData, 6, 8); - name = new String(timeZoneData, 8, nameLen, "UTF-16LE"); - int ruleOffset = 8+nameLen; - int nRules = (int)PSTObject.convertLittleEndianBytesToLong(timeZoneData, ruleOffset, ruleOffset+2); - - ruleOffset = 4 + headerLen; - for ( int rule = 0; rule < nRules; ++rule ) { - // Is this rule the effective rule? - int flags = (int)PSTObject.convertLittleEndianBytesToLong(timeZoneData, ruleOffset+4, ruleOffset+6); - if ( (flags & 0x0002) != 0 ) { - this.rule = new TZRule(timeZoneData, ruleOffset+6); - break; - } - ruleOffset += 66; - } - } - catch ( Exception e ) { - System.err.printf("Exception reading timezone: %s\n", e.toString()); - e.printStackTrace(); - this.rule = null; - name = ""; - } - } - - PSTTimeZone(String name, byte[] timeZoneData) { - this.name = name; - this.rule = null; - - try { - this.rule = new TZRule(new SYSTEMTIME(), timeZoneData, 0); - } - catch ( Exception e ) { - System.err.printf("Exception reading timezone: %s\n", e.toString()); - e.printStackTrace(); - this.rule = null; - name = ""; - } - } - - public String getName() { - return name; - } - - public SimpleTimeZone getSimpleTimeZone() { - if ( simpleTimeZone != null ) { - return simpleTimeZone; - } - - if ( rule.startStandard.wMonth == 0 ) { - // A time zone with no daylight savings time - simpleTimeZone = new SimpleTimeZone((rule.lBias+rule.lStandardBias) * 60 * 1000, name); - - return simpleTimeZone; - } - - int startMonth = (rule.startDaylight.wMonth -1 ) + Calendar.JANUARY; - int startDayOfMonth = (rule.startDaylight.wDay == 5) ? -1 : ((rule.startDaylight.wDay - 1) * 7) + 1; - int startDayOfWeek = rule.startDaylight.wDayOfWeek + Calendar.SUNDAY; - int endMonth = (rule.startStandard.wMonth -1 ) + Calendar.JANUARY; - int endDayOfMonth = (rule.startStandard.wDay == 5) ? -1 : ((rule.startStandard.wDay - 1) * 7) + 1; - int endDayOfWeek = rule.startStandard.wDayOfWeek + Calendar.SUNDAY; - int savings = (rule.lStandardBias-rule.lDaylightBias) * 60 * 1000; - - simpleTimeZone = new SimpleTimeZone( - -((rule.lBias+rule.lStandardBias) * 60 * 1000), - name, - startMonth, startDayOfMonth, -startDayOfWeek, - (((((rule.startDaylight.wHour * 60) + - rule.startDaylight.wMinute) * 60) + - rule.startDaylight.wSecond) * 1000) + - rule.startDaylight.wMilliseconds, - endMonth, endDayOfMonth, -endDayOfWeek, - (((((rule.startStandard.wHour * 60) + - rule.startStandard.wMinute) * 60) + - rule.startStandard.wSecond) * 1000) + - rule.startStandard.wMilliseconds, - savings - ); - - return simpleTimeZone; - } - - public boolean isEqual(PSTTimeZone rhs) { - if ( name.equalsIgnoreCase(rhs.name) ) { - if ( rule.isEqual(rhs.rule) ) { - return true; - } - - System.err.printf("Warning: different timezones with the same name: %s\n", name); - } - return false; - } - - public SYSTEMTIME getStart() { - return rule.dtStart; - } - - public int getBias() { - return rule.lBias; - } - - public int getStandardBias() { - return rule.lStandardBias; - } - - public int getDaylightBias() { - return rule.lDaylightBias; - } - - public SYSTEMTIME getDaylightStart() { - return rule.startDaylight; - } - - public SYSTEMTIME getStandardStart() { - return rule.startStandard; - } - - public class SYSTEMTIME { - - SYSTEMTIME() { - wYear = 0; - wMonth = 0; - wDayOfWeek = 0; - wDay = 0; - wHour = 0; - wMinute = 0; - wSecond = 0; - wMilliseconds = 0; - } - - SYSTEMTIME(byte[] timeZoneData, int offset) { - wYear = (short)(PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset, offset+2)&0x7FFF); - wMonth = (short)(PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset+2, offset+4)&0x7FFF); - wDayOfWeek = (short)(PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset+4, offset+6)&0x7FFF); - wDay = (short)(PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset+6, offset+8)&0x7FFF); - wHour = (short)(PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset+8, offset+10)&0x7FFF); - wMinute = (short)(PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset+10, offset+12)&0x7FFF); - wSecond = (short)(PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset+12, offset+14)&0x7FFF); - wMilliseconds = (short)(PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset+14, offset+16)&0x7FFF); - } - - boolean isEqual(SYSTEMTIME rhs) { - return wYear == rhs.wYear && - wMonth == rhs.wMonth && - wDayOfWeek == rhs.wDayOfWeek && - wDay == rhs.wDay && - wHour == rhs.wHour && - wMinute == rhs.wMinute && - wSecond == rhs.wSecond && - wMilliseconds == rhs.wMilliseconds; - } - - public short wYear; - public short wMonth; - public short wDayOfWeek; - public short wDay; - public short wHour; - public short wMinute; - public short wSecond; - public short wMilliseconds; - } - - /** - * A static copy of the UTC time zone, available for others to use - */ - public static SimpleTimeZone utcTimeZone = new SimpleTimeZone(0, "UTC"); - - private class TZRule { - - TZRule(SYSTEMTIME dtStart, byte[] timeZoneData, int offset) { - this.dtStart = dtStart; - InitBiases(timeZoneData, offset); - @SuppressWarnings("unused") - short wStandardYear = (short)PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset+12, offset+14); - startStandard = new SYSTEMTIME(timeZoneData, offset+14); - @SuppressWarnings("unused") - short wDaylightYear = (short)PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset+30, offset+32); - startDaylight = new SYSTEMTIME(timeZoneData, offset+32); - } - - TZRule(byte[] timeZoneData, int offset) { - dtStart = new SYSTEMTIME(timeZoneData, offset); - InitBiases(timeZoneData, offset+16); - startStandard = new SYSTEMTIME(timeZoneData, offset+28); - startDaylight = new SYSTEMTIME(timeZoneData, offset+44); - } - - private void InitBiases(byte[] timeZoneData, int offset) { - lBias = (int)PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset, offset+4); - lStandardBias = (int)PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset+4, offset+8); - lDaylightBias = (int)PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset+8, offset+12); - } - - boolean isEqual(TZRule rhs) { - return dtStart.isEqual(rhs.dtStart) && - lBias == rhs.lBias && - lStandardBias == rhs.lStandardBias && - lDaylightBias == rhs.lDaylightBias && - startStandard.isEqual(rhs.startStandard) && - startDaylight.isEqual(rhs.startDaylight); - } - - SYSTEMTIME dtStart; - int lBias; - int lStandardBias; - int lDaylightBias; - SYSTEMTIME startStandard; - SYSTEMTIME startDaylight; - } - - private String name; - private TZRule rule; - private SimpleTimeZone simpleTimeZone = null; -} diff --git a/example/Test.java b/example/Test.java index b95d13b..112540d 100644 --- a/example/Test.java +++ b/example/Test.java @@ -1,16 +1,23 @@ package example; -import com.pff.*; -import java.util.*; +import java.util.ArrayList; + +import com.pff.exceptions.PSTException; +import com.pff.objects.PSTFolder; +import com.pff.objects.PSTMessage; +import com.pff.source.PSTRandomAccessFile; +import com.pff.source.PSTSource; +import com.pff.source._RandomAccessPSTSource; public class Test { - public static void main(String[] args) - { + + public static void main(String[] args) { new Test(args[0]); } public Test(String filename) { try { - PSTFile pstFile = new PSTFile(filename); + _RandomAccessPSTSource raSrc = new PSTRandomAccessFile(filename); + PSTSource pstFile = new PSTSource(raSrc); System.out.println(pstFile.getMessageStore().getDisplayName()); processFolder(pstFile.getRootFolder()); } catch (Exception err) { @@ -19,9 +26,7 @@ public Test(String filename) { } int depth = -1; - public void processFolder(PSTFolder folder) - throws PSTException, java.io.IOException - { + public void processFolder(PSTFolder folder) throws PSTException, java.io.IOException { depth++; // the root folder doesn't have a display name if (depth > 0) { @@ -31,7 +36,7 @@ public void processFolder(PSTFolder folder) // go through the folders... if (folder.hasSubfolders()) { - Vector childFolders = folder.getSubFolders(); + ArrayList childFolders = folder.getSubFolders(); for (PSTFolder childFolder : childFolders) { processFolder(childFolder); } diff --git a/example/TestGui.java b/example/TestGui.java index 6672855..a945dbb 100644 --- a/example/TestGui.java +++ b/example/TestGui.java @@ -4,26 +4,62 @@ package example; -import java.awt.*; -import java.awt.event.*; - -import javax.swing.*; -import javax.swing.event.*; -import javax.swing.table.AbstractTableModel; +import java.awt.BorderLayout; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; import javax.swing.JMenuItem; -import java.io.*; -import javax.swing.tree.*; - -import com.pff.*; - -import java.util.*; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.JTextPane; +import javax.swing.JTree; +import javax.swing.ListSelectionModel; +import javax.swing.WindowConstants; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.table.AbstractTableModel; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; + +import com.pff.exceptions.PSTException; +import com.pff.objects.PSTActivity; +import com.pff.objects.PSTAttachment; +import com.pff.objects.PSTContact; +import com.pff.objects.PSTFolder; +import com.pff.objects.PSTMessage; +import com.pff.objects.PSTMessageStore; +import com.pff.objects.PSTObject; +import com.pff.objects.PSTRss; +import com.pff.objects.PSTTask; +import com.pff.source.PSTRandomAccessFile; +import com.pff.source.PSTSource; /** * @author toweruser * */ public class TestGui implements ActionListener { - private PSTFile pstFile; + private PSTSource pstFile; private EmailTableModel emailTableModel; private JTextPane emailText; private JPanel emailPanel; @@ -40,7 +76,7 @@ public TestGui() throws PSTException, IOException { // attempt to open the pst file try { - /* + JFileChooser chooser = new JFileChooser(); if (chooser.showOpenDialog(f) == JFileChooser.APPROVE_OPTION) { } else { @@ -48,13 +84,12 @@ public TestGui() throws PSTException, IOException { } String filename = chooser.getSelectedFile().getCanonicalPath(); - */ - String filename = "Outlook-new.pst"; - filename = "G:\\From old Tower\\pff\\java\\Old Email.pst"; + + /*String filename = "Outlook-new.pst"; + filename = "G:\\From old Tower\\pff\\java\\Old Email.pst";*/ //filename = "RichardJohnson@sumac.uk.com - exchange.ost"; //String filename = "Outlook 32bit.pst"; - //String russian = "Узеи́р Абду́л-Гусе́йн оглы́ Гаджибе́ков (азерб. Üzeyir bəy Əbdülhüseyn oğlu Hacıbəyov; 18 сентября 1885, Агджабеди, Шушинский уезд, Елизаветпольская губерния, Российская империя — 23 ноября 1948, Баку, Азербайджанская ССР, СССР) — азербайджанский композитор, дирижёр, публицист, фельетонист, драматург и педагог, народный артист СССР (1938), дважды лауреат Сталинских премий (1941, 1946). Действительный член АН Азербайджана (1945), профессор (1940), ректор Азербайджанской государственной "; - + //System.out.println(java.nio.charset.Charset.availableCharsets()); //byte[] russianBytes = russian.getBytes("UTF-8"); @@ -62,7 +97,8 @@ public TestGui() throws PSTException, IOException { //String filename = "Outlook 32bit.pst"; //filename = "RichardJohnson@sumac.uk.com - exchange.ost"; - pstFile = new PSTFile(filename); + PSTRandomAccessFile pstRaF = new PSTRandomAccessFile(filename); + pstFile = new PSTSource(pstRaF); //pstFile = new PSTFile("RichardJohnson@sumac.uk.com - exchange.ost"); @@ -175,7 +211,9 @@ public TestGui() throws PSTException, IOException { } final JTree folderTree = new JTree(top){ - public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + private static final long serialVersionUID = 1L; + + public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { DefaultMutableTreeNode nodeValue = (DefaultMutableTreeNode)value; if (nodeValue.getUserObject() instanceof PSTFolder) { PSTFolder folderValue = (PSTFolder)nodeValue.getUserObject(); @@ -293,14 +331,14 @@ public void valueChanged(ListSelectionEvent e) { // Set the visibility as true, thereby displaying it f.setVisible(true); // f.setSize(800, 600); - f.setExtendedState(f.getExtendedState() | f.MAXIMIZED_BOTH); + f.setExtendedState(f.getExtendedState() | JFrame.MAXIMIZED_BOTH); } private void buildTree(DefaultMutableTreeNode top, PSTFolder theFolder) { // this is recursive, try and keep up. try { - Vector children = theFolder.getSubFolders(); - Iterator childrenIterator = children.iterator(); + ArrayList children = theFolder.getSubFolders(); + Iterator childrenIterator = children.iterator(); while (childrenIterator.hasNext()) { PSTFolder folder = (PSTFolder)childrenIterator.next(); @@ -429,13 +467,14 @@ public static void main(String[] args) throws PSTException, IOException { } class EmailTableModel extends AbstractTableModel { + private static final long serialVersionUID = 6414607674310021030L; PSTFolder theFolder = null; - PSTFile theFile = null; + PSTSource theFile = null; - HashMap cache = new HashMap(); + HashMap cache = new HashMap(); - public EmailTableModel(PSTFolder theFolder, PSTFile theFile) { + public EmailTableModel(PSTFolder theFolder, PSTSource theFile) { super(); this.theFolder = theFolder; @@ -527,7 +566,7 @@ public void setFolder(PSTFolder theFolder) { theFolder.moveChildCursorTo(0); this.theFolder = theFolder; - cache = new HashMap(); + cache = new HashMap(); this.fireTableDataChanged(); } From 76a32cddb4db58321c80460bd50b525658ce216c Mon Sep 17 00:00:00 2001 From: casimirenslip Date: Mon, 29 Apr 2013 12:36:17 +0200 Subject: [PATCH 2/3] - --- .classpath | 6 + .project | 17 + com/pff/PSTUtils.java | 376 ++++++ .../exceptions/PSTAppointmentException.java | 385 ++++++ com/pff/exceptions/PSTException.java | 53 + com/pff/objects/PSTActivity.java | 151 +++ com/pff/objects/PSTAppointment.java | 200 ++++ com/pff/objects/PSTAttachment.java | 263 +++++ com/pff/objects/PSTContact.java | 870 ++++++++++++++ com/pff/objects/PSTFolder.java | 420 +++++++ com/pff/objects/PSTMessage.java | 1029 +++++++++++++++++ com/pff/objects/PSTMessageStore.java | 97 ++ com/pff/objects/PSTObject.java | 549 +++++++++ com/pff/objects/PSTRss.java | 127 ++ com/pff/objects/PSTTask.java | 197 ++++ .../objects/sub/PSTAppointmentRecurrence.java | 316 +++++ com/pff/objects/sub/PSTRecipient.java | 154 +++ com/pff/objects/sub/PSTTimeZone.java | 270 +++++ com/pff/parsing/DescriptorIndexNode.java | 104 ++ com/pff/parsing/OffsetIndexItem.java | 94 ++ com/pff/parsing/PSTDescriptorItem.java | 132 +++ com/pff/parsing/PSTNodeInputStream.java | 464 ++++++++ com/pff/parsing/tables/PSTTable.java | 274 +++++ com/pff/parsing/tables/PSTTable7C.java | 427 +++++++ com/pff/parsing/tables/PSTTable7CItem.java | 46 + com/pff/parsing/tables/PSTTableBC.java | 207 ++++ com/pff/parsing/tables/PSTTableBCItem.java | 46 + com/pff/parsing/tables/PSTTableItem.java | 198 ++++ com/pff/source/PSTRandomAccessFile.java | 57 + com/pff/source/PSTSource.java | 918 +++++++++++++++ com/pff/source/_RandomAccessPSTSource.java | 22 + deprecated/LZFu.java | 49 + deprecated/PSTFile.java | 938 +++++++++++++++ example/TestRandomAccess.java | 15 + 34 files changed, 9471 insertions(+) create mode 100644 .classpath create mode 100644 .project create mode 100644 com/pff/PSTUtils.java create mode 100644 com/pff/exceptions/PSTAppointmentException.java create mode 100644 com/pff/exceptions/PSTException.java create mode 100644 com/pff/objects/PSTActivity.java create mode 100644 com/pff/objects/PSTAppointment.java create mode 100644 com/pff/objects/PSTAttachment.java create mode 100644 com/pff/objects/PSTContact.java create mode 100644 com/pff/objects/PSTFolder.java create mode 100644 com/pff/objects/PSTMessage.java create mode 100644 com/pff/objects/PSTMessageStore.java create mode 100644 com/pff/objects/PSTObject.java create mode 100644 com/pff/objects/PSTRss.java create mode 100644 com/pff/objects/PSTTask.java create mode 100644 com/pff/objects/sub/PSTAppointmentRecurrence.java create mode 100644 com/pff/objects/sub/PSTRecipient.java create mode 100644 com/pff/objects/sub/PSTTimeZone.java create mode 100644 com/pff/parsing/DescriptorIndexNode.java create mode 100644 com/pff/parsing/OffsetIndexItem.java create mode 100644 com/pff/parsing/PSTDescriptorItem.java create mode 100644 com/pff/parsing/PSTNodeInputStream.java create mode 100644 com/pff/parsing/tables/PSTTable.java create mode 100644 com/pff/parsing/tables/PSTTable7C.java create mode 100644 com/pff/parsing/tables/PSTTable7CItem.java create mode 100644 com/pff/parsing/tables/PSTTableBC.java create mode 100644 com/pff/parsing/tables/PSTTableBCItem.java create mode 100644 com/pff/parsing/tables/PSTTableItem.java create mode 100644 com/pff/source/PSTRandomAccessFile.java create mode 100644 com/pff/source/PSTSource.java create mode 100644 com/pff/source/_RandomAccessPSTSource.java create mode 100644 deprecated/LZFu.java create mode 100644 deprecated/PSTFile.java create mode 100644 example/TestRandomAccess.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..6bdca60 --- /dev/null +++ b/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..42ac320 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + java-libpst + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/com/pff/PSTUtils.java b/com/pff/PSTUtils.java new file mode 100644 index 0000000..402b223 --- /dev/null +++ b/com/pff/PSTUtils.java @@ -0,0 +1,376 @@ +package com.pff; + +import java.io.UnsupportedEncodingException; +import java.util.Calendar; +import java.util.Date; + +import com.pff.exceptions.PSTException; +import com.pff.objects.sub.PSTTimeZone; + +public abstract class PSTUtils { + + public static final String LZFU_HEADER = "{\\rtf1\\ansi\\mac\\deff0\\deftab720{\\fonttbl;}{\\f0\\fnil \\froman \\fswiss \\fmodern \\fscript \\fdecor MS Sans SerifSymbolArialTimes New RomanCourier{\\colortbl\\red0\\green0\\blue0\n\r\\par \\pard\\plain\\f0\\fs20\\b\\i\\u\\tab\\tx"; + + + // substitution table for the compressible encryption type. + static int[] compEnc = { + 0x47, 0xf1, 0xb4, 0xe6, 0x0b, 0x6a, 0x72, 0x48, 0x85, 0x4e, 0x9e, 0xeb, 0xe2, 0xf8, 0x94, 0x53, + 0xe0, 0xbb, 0xa0, 0x02, 0xe8, 0x5a, 0x09, 0xab, 0xdb, 0xe3, 0xba, 0xc6, 0x7c, 0xc3, 0x10, 0xdd, + 0x39, 0x05, 0x96, 0x30, 0xf5, 0x37, 0x60, 0x82, 0x8c, 0xc9, 0x13, 0x4a, 0x6b, 0x1d, 0xf3, 0xfb, + 0x8f, 0x26, 0x97, 0xca, 0x91, 0x17, 0x01, 0xc4, 0x32, 0x2d, 0x6e, 0x31, 0x95, 0xff, 0xd9, 0x23, + 0xd1, 0x00, 0x5e, 0x79, 0xdc, 0x44, 0x3b, 0x1a, 0x28, 0xc5, 0x61, 0x57, 0x20, 0x90, 0x3d, 0x83, + 0xb9, 0x43, 0xbe, 0x67, 0xd2, 0x46, 0x42, 0x76, 0xc0, 0x6d, 0x5b, 0x7e, 0xb2, 0x0f, 0x16, 0x29, + 0x3c, 0xa9, 0x03, 0x54, 0x0d, 0xda, 0x5d, 0xdf, 0xf6, 0xb7, 0xc7, 0x62, 0xcd, 0x8d, 0x06, 0xd3, + 0x69, 0x5c, 0x86, 0xd6, 0x14, 0xf7, 0xa5, 0x66, 0x75, 0xac, 0xb1, 0xe9, 0x45, 0x21, 0x70, 0x0c, + 0x87, 0x9f, 0x74, 0xa4, 0x22, 0x4c, 0x6f, 0xbf, 0x1f, 0x56, 0xaa, 0x2e, 0xb3, 0x78, 0x33, 0x50, + 0xb0, 0xa3, 0x92, 0xbc, 0xcf, 0x19, 0x1c, 0xa7, 0x63, 0xcb, 0x1e, 0x4d, 0x3e, 0x4b, 0x1b, 0x9b, + 0x4f, 0xe7, 0xf0, 0xee, 0xad, 0x3a, 0xb5, 0x59, 0x04, 0xea, 0x40, 0x55, 0x25, 0x51, 0xe5, 0x7a, + 0x89, 0x38, 0x68, 0x52, 0x7b, 0xfc, 0x27, 0xae, 0xd7, 0xbd, 0xfa, 0x07, 0xf4, 0xcc, 0x8e, 0x5f, + 0xef, 0x35, 0x9c, 0x84, 0x2b, 0x15, 0xd5, 0x77, 0x34, 0x49, 0xb6, 0x12, 0x0a, 0x7f, 0x71, 0x88, + 0xfd, 0x9d, 0x18, 0x41, 0x7d, 0x93, 0xd8, 0x58, 0x2c, 0xce, 0xfe, 0x24, 0xaf, 0xde, 0xb8, 0x36, + 0xc8, 0xa1, 0x80, 0xa6, 0x99, 0x98, 0xa8, 0x2f, 0x0e, 0x81, 0x65, 0x73, 0xe4, 0xc2, 0xa2, 0x8a, + 0xd4, 0xe1, 0x11, 0xd0, 0x08, 0x8b, 0x2a, 0xf2, 0xed, 0x9a, 0x64, 0x3f, 0xc1, 0x6c, 0xf9, 0xec + }; + + /** + * Output a dump of data in hex format in the order it was read in + * @param data + * @param pretty + */ + public static void printHexFormatted(byte[] data, boolean pretty) { + printHexFormatted(data,pretty, new int[0]); + } + protected static void printHexFormatted(byte[] data, boolean pretty, int[] indexes) { + // groups of two + if (pretty) { System.out.println("---"); } + long tmpLongValue; + String line = ""; + int nextIndex = 0; + int indexIndex = 0; + if (indexes.length > 0) { + nextIndex = indexes[0]; + indexIndex++; + } + for (int x = 0; x < data.length; x++) { + tmpLongValue = (long)data[x] & 0xff; + + if (indexes.length > 0 && + x == nextIndex && + nextIndex < data.length) + { + System.out.print("+"); + line += "+"; + while (indexIndex < indexes.length-1 && indexes[indexIndex] <= nextIndex) + { + indexIndex++; + } + nextIndex = indexes[indexIndex]; + //indexIndex++; + } + + if (Character.isLetterOrDigit((char)tmpLongValue)) { + line += (char)tmpLongValue; + } + else + { + line += "."; + } + + if (Long.toHexString(tmpLongValue).length() < 2) { + System.out.print("0"); + } + System.out.print(Long.toHexString(tmpLongValue)); + if (x % 2 == 1 && pretty) { + System.out.print(" "); + } + if (x % 16 == 15 && pretty) { + System.out.print(" "+line); + System.out.println(""); + line = ""; + } + } + if (pretty) { System.out.println(" "+line); System.out.println("---"); System.out.println(data.length); } else { } + } + + + + /** + * decode a lump of data that has been encrypted with the compressible encryption + * @param data + * @return decoded data + */ + public static byte[] decode(byte[] data) { + int temp; + for (int x = 0; x < data.length; x++) { + temp = data[x] & 0xff; + data[x] = (byte)compEnc[temp]; + } + return data; + } + + + public static byte[] encode(byte[] data) { + // create the encoding array... + int[] enc = new int[compEnc.length]; + for (int x = 0; x < enc.length; x++) { + enc[compEnc[x]] = x; + } + + // now it's just the same as decode... + int temp; + for (int x = 0; x < data.length; x++) { + temp = data[x] & 0xff; + data[x] = (byte)enc[temp]; + } + return data; + } + + + /** + * Utility function for converting little endian bytes into a usable java long + * @param data + * @return long version of the data + */ + public static long convertLittleEndianBytesToLong(byte[] data) { + return convertLittleEndianBytesToLong(data, 0, data.length); + } + /** + * Utility function for converting little endian bytes into a usable java long + * @param data + * @param start + * @param end + * @return long version of the data + */ + public static long convertLittleEndianBytesToLong(byte[] data, int start, int end) { + + long offset = data[end-1] & 0xff; + long tmpLongValue; + for (int x = end-2; x >= start; x--) { + offset = offset << 8; + tmpLongValue = (long)data[x] & 0xff; + offset |= tmpLongValue; + } + + return offset; + } + + /** + * Utility function for converting big endian bytes into a usable java long + * @param data + * @param start + * @param end + * @return long version of the data + */ + public static long convertBigEndianBytesToLong(byte[] data, int start, int end) { + + long offset = 0; + for ( int x = start; x < end; ++x ) { + offset = offset << 8; + offset |= ((long)data[x] & 0xFFL); + } + + return offset; + } +/* + protected static boolean isPSTArray(byte[] data) { + return (data[0] == 1 && data[1] == 1); + } +/**/ +/* + protected static int[] getBlockOffsets(RandomAccessFile in, byte[] data) + throws IOException, PSTException + { + // is the data an array? + if (!(data[0] == 1 && data[1] == 1)) + { + throw new PSTException("Unable to process array, does not appear to be one!"); + } + + // we are an array! + // get the array items and merge them together + int numberOfEntries = (int)PSTObject.convertLittleEndianBytesToLong(data, 2, 4); + int[] output = new int[numberOfEntries]; + int tableOffset = 8; + int blockOffset = 0; + for (int y = 0; y < numberOfEntries; y++) { + // get the offset identifier + long tableOffsetIdentifierIndex = PSTObject.convertLittleEndianBytesToLong(data, tableOffset, tableOffset+8); + // clear the last bit of the identifier. Why so hard? + tableOffsetIdentifierIndex = (tableOffsetIdentifierIndex & 0xfffffffe); + OffsetIndexItem tableOffsetIdentifier = PSTObject.getOffsetIndexNode(in, tableOffsetIdentifierIndex); + blockOffset += tableOffsetIdentifier.size; + output[y] = blockOffset; + tableOffset += 8; + } + + // replace the item data with the stuff from the array... + return output; + } +/**/ + + + + + + /** + * the code below was taken from a random apache project + * http://www.koders.com/java/fidA9D4930E7443F69F32571905DD4CA01E4D46908C.aspx + * my bit-shifting isn't that 1337 + */ + + /** + *

The difference between the Windows epoch (1601-01-01 + * 00:00:00) and the Unix epoch (1970-01-01 00:00:00) in + * milliseconds: 11644473600000L. (Use your favorite spreadsheet + * program to verify the correctness of this value. By the way, + * did you notice that you can tell from the epochs which + * operating system is the modern one? :-))

+ */ + private static final long EPOCH_DIFF = 11644473600000L; + + /** + *

Converts a Windows FILETIME into a {@link Date}. The Windows + * FILETIME structure holds a date and time associated with a + * file. The structure identifies a 64-bit integer specifying the + * number of 100-nanosecond intervals which have passed since + * January 1, 1601. This 64-bit value is split into the two double + * words stored in the structure.

+ * + * @param high The higher double word of the FILETIME structure. + * @param low The lower double word of the FILETIME structure. + * @return The Windows FILETIME as a {@link Date}. + */ + public static Date filetimeToDate(final int high, final int low) { + final long filetime = ((long) high) << 32 | (low & 0xffffffffL); + //System.out.printf("0x%X\n", filetime); + final long ms_since_16010101 = filetime / (1000 * 10); + final long ms_since_19700101 = ms_since_16010101 - EPOCH_DIFF; + return new Date(ms_since_19700101); + } + + public static Calendar apptTimeToCalendar(int minutes) { + final long ms_since_16010101 = minutes * (60*1000L); + final long ms_since_19700101 = ms_since_16010101 - EPOCH_DIFF; + Calendar c = Calendar.getInstance(PSTTimeZone.utcTimeZone); + c.setTimeInMillis(ms_since_19700101); + return c; + } + + public static Calendar apptTimeToUTC(int minutes, PSTTimeZone tz) { + // Must convert minutes since 1/1/1601 in local time to UTC + // There's got to be a better way of doing this... + // First get a UTC calendar object that contains _local time_ + Calendar cUTC = apptTimeToCalendar(minutes); + if ( tz != null ) { + // Create an empty Calendar object with the required time zone + Calendar cLocal = Calendar.getInstance(tz.getSimpleTimeZone()); + cLocal.clear(); + + // Now transfer the local date/time from the UTC calendar object + // to the object that knows about the time zone... + cLocal.set(cUTC.get(Calendar.YEAR), + cUTC.get(Calendar.MONTH), + cUTC.get(Calendar.DATE), + cUTC.get(Calendar.HOUR_OF_DAY), + cUTC.get(Calendar.MINUTE), + cUTC.get(Calendar.SECOND)); + + // Get the true UTC from the local time calendar object. + // Drop any milliseconds, they won't be printed anyway! + long utcs = cLocal.getTimeInMillis() / 1000; + + // Finally, set the true UTC in the UTC calendar object + cUTC.setTimeInMillis(utcs * 1000); + } // else hope for the best! + + return cUTC; + } + + + + + public static int getCompEnc(int value) { + return compEnc[value]; + } + + + public static String decodeLZFU(byte[] data) throws PSTException { + + @SuppressWarnings("unused") + int compressedSize = (int)PSTUtils.convertLittleEndianBytesToLong(data, 0, 4); + int uncompressedSize = (int)PSTUtils.convertLittleEndianBytesToLong(data, 4, 8); + int compressionSig = (int)PSTUtils.convertLittleEndianBytesToLong(data, 8, 12); + @SuppressWarnings("unused") + int compressedCRC = (int)PSTUtils.convertLittleEndianBytesToLong(data, 12, 16); + + if (compressionSig == 0x75465a4c) { + // we are compressed... + byte[] output = new byte[uncompressedSize]; + int outputPosition = 0; + byte[] lzBuffer = new byte[4096]; + // preload our buffer. + try { + byte[] bytes = LZFU_HEADER.getBytes("US-ASCII"); + System.arraycopy(bytes, 0, lzBuffer, 0, LZFU_HEADER.length()); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + int bufferPosition = LZFU_HEADER.length(); + int currentDataPosition = 16; + + // next byte is the flags, + while (currentDataPosition < data.length - 2 && outputPosition < output.length) { + int flags = data[currentDataPosition++] & 0xFF; + for (int x = 0; x < 8 && outputPosition < output.length; x++) { + boolean isRef = ((flags & 1) == 1); + flags >>= 1; + if (isRef) { + // get the starting point for the buffer and the + // length to read + int refOffsetOrig = data[currentDataPosition++] & 0xFF; + int refSizeOrig = data[currentDataPosition++] & 0xFF; + int refOffset = (refOffsetOrig << 4) | (refSizeOrig >>> 4); + int refSize = (refSizeOrig & 0xF) + 2; + //refOffset &= 0xFFF; + try { + // copy the data from the buffer + int index = refOffset; + for (int y = 0; y < refSize && outputPosition < output.length; y++) { + output[outputPosition++] = lzBuffer[index]; + lzBuffer[bufferPosition] = lzBuffer[index]; + bufferPosition++; + bufferPosition %= 4096; + ++index; + index %= 4096; + } + } catch ( Exception e ) { + e.printStackTrace(); + } + + } else { + // copy the byte over + lzBuffer[bufferPosition] = data[currentDataPosition]; + bufferPosition++; + bufferPosition %= 4096; + output[outputPosition++] = data[currentDataPosition++]; + } + } + } + + if ( outputPosition != uncompressedSize ) { + throw new PSTException(String.format("Error decompressing RTF! Expected %d bytes, got %d bytes\n", uncompressedSize, outputPosition)); + } + return new String(output).trim(); + + } else if (compressionSig == 0x414c454d) { + // we are not compressed! + // just return the rest of the contents as a string + byte[] output = new byte[data.length-16]; + System.arraycopy(data, 16, output, 0, data.length-16); + return new String(output).trim(); + } + + return ""; + } + +} diff --git a/com/pff/exceptions/PSTAppointmentException.java b/com/pff/exceptions/PSTAppointmentException.java new file mode 100644 index 0000000..8e88ba1 --- /dev/null +++ b/com/pff/exceptions/PSTAppointmentException.java @@ -0,0 +1,385 @@ +/** + * 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.exceptions; + +import java.io.UnsupportedEncodingException; +import java.util.Calendar; +import java.util.Date; + +import com.pff.PSTUtils; +import com.pff.objects.PSTAppointment; +import com.pff.objects.sub.PSTTimeZone; + +/** + * Class containing information on exceptions to a recurring appointment + * @author Orin Eman + * + * + */ +public class PSTAppointmentException { + + // Access methods - return the value from the exception if + // OverrideFlags say it's present, otherwise the value from the appointment. + public String getSubject() { + if ( (overrideFlags & 0x0001) != 0 ) { + try { + return new String(wideCharSubject, "UTF-16LE"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + return this.appt.getSubject(); + } + + + public int getMeetingType() + { + if ( (overrideFlags & 0x0002) != 0 ) { + return meetingType; + } + + return appt.getMeetingStatus(); + } + + + public int getReminderDelta() { + if ( (overrideFlags & 0x0004) != 0 ) { + return ReminderDelta; + } + + return appt.getReminderDelta(); + } + + + public boolean getReminderSet() { + if ( (overrideFlags & 0x0008) != 0 ) { + return ReminderSet; + } + + return appt.getReminderSet(); + } + + + public String getLocation() { + if ( (overrideFlags & 0x0010) != 0 ) { + try { + return new String(wideCharLocation, "UTF-16LE"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + return appt.getLocation(); + } + + + public int getBusyStatus() { + if ( (overrideFlags & 0x0020) != 0 ) { + return busyStatus; + } + + return appt.getBusyStatus(); + } + + + public boolean getSubType() { + if ( (overrideFlags & 0x0080) != 0 ) { + return subType; + } + + return appt.getSubType(); + } + + public String getDescription() + { + if ( embeddedMessage != null ) { + return embeddedMessage.getBodyPrefix(); + } + + return null; + } + + public Date getDTStamp() { + Date ret = null; + if ( embeddedMessage != null ) { + ret = embeddedMessage.getOwnerCriticalChange(); + } + + if ( ret == null ) { + // Use current date/time + Calendar c = Calendar.getInstance(PSTTimeZone.utcTimeZone); + ret = c.getTime(); + } + + return ret; + } + + public int getStartDateTime() { + return startDateTime; + } + + public int getEndDateTime() { + return endDateTime; + } + + public int getOriginalStartDate() { + return originalStartDate; + } + + public int getAppointmentSequence(int def) { + if ( embeddedMessage == null ) { + return def; + } + return embeddedMessage.getAppointmentSequence(); + } + + public int getImportance(int def) { + if ( embeddedMessage == null ) { + return def; + } + return embeddedMessage.getImportance(); + } + + public byte[] getSubjectBytes() { + if ( (overrideFlags & 0x0010) != 0 ) { + return Subject; + } + return null; + } + + public byte[] getLocationBytes() { + if ( (overrideFlags & 0x0010) != 0 ) { + return location; + } + return null; + } + + public boolean attachmentsPresent() { + if ( (overrideFlags & 0x0040) != 0 && attachment == 0x00000001 ) { + return true; + } + return false; + } + + public boolean embeddedMessagePresent() { + return embeddedMessage != null; + } + + // + // Allow access to an embedded message for + // properties that don't have access methods here. + // + public PSTAppointment getEmbeddedMessage() { + return embeddedMessage; + } + + public PSTAppointmentException(byte[] recurrencePattern, int offset, int writerVersion2, PSTAppointment appt) { + this.writerVersion2 = writerVersion2; + int initialOffset = offset; + this.appt = appt; + embeddedMessage = null; + + startDateTime = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + endDateTime = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + originalStartDate = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + overrideFlags = (short)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); + offset += 2; + + if ( (overrideFlags & ARO_SUBJECT) != 0 ) { + //@SuppressWarnings("unused") + //short SubjectLength = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); + offset += 2; + short SubjectLength2 = (short)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); + offset += 2; + Subject = new byte[SubjectLength2]; + System.arraycopy(recurrencePattern, offset, Subject, 0, SubjectLength2); + offset += SubjectLength2; + } + + if ( (overrideFlags & ARO_MEETINGTYPE) != 0 ) { + meetingType = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + } + + if ( (overrideFlags & ARO_REMINDERDELTA) != 0 ) { + ReminderDelta = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + } + + if ( (overrideFlags & ARO_REMINDER) != 0 ) { + ReminderSet = ((int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4) != 0); + offset += 4; + } + + if ( (overrideFlags & ARO_LOCATION) != 0 ) { + //@SuppressWarnings("unused") + //short LocationLength = (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); + offset += 2; + short LocationLength2 = (short)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); + offset += 2; + location = new byte[LocationLength2]; + System.arraycopy(recurrencePattern, offset, location, 0, LocationLength2); + offset += LocationLength2; + } + + if ( (overrideFlags & ARO_BUSYSTATUS) != 0 ) { + busyStatus = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + } + + if ( (overrideFlags & ARO_ATTACHMENT) != 0 ) { + attachment = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + } + + if ( (overrideFlags & ARO_SUBTYPE) != 0 ) { + subType = ((int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4) != 0); + offset += 4; + } + + length = offset - initialOffset; + } + + public void buildExtendedException(byte[] recurrencePattern, int offset) { + int initialOffset = offset; + + if ( writerVersion2 >= 0x00003009 ) { + int ChangeHighlightSize = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + changeHighlightValue = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += ChangeHighlightSize; + } + + int ReservedBlockEESize = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4 + ReservedBlockEESize; + + // See http://msdn.microsoft.com/en-us/library/cc979209(office.12).aspx + if ( (overrideFlags & (ARO_SUBJECT|ARO_LOCATION)) != 0 ) { + // Same as regular Exception structure? + startDateTime = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + endDateTime = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + originalStartDate = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + } + + if ( (overrideFlags & ARO_SUBJECT) != 0 ) { + wideCharSubjectLength = (short)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); + offset += 2; + wideCharSubject = new byte[wideCharSubjectLength * 2]; + System.arraycopy(recurrencePattern, offset, wideCharSubject, 0, wideCharSubject.length); + offset += wideCharSubject.length; +/* + try { + String subject = new String(WideCharSubject, "UTF-16LE"); + System.out.printf("Exception Subject: %s\n", subject); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } +/**/ + } + + if ( (overrideFlags & ARO_LOCATION) != 0 ) { + wideCharLocationLength = (short)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); + offset += 2; + wideCharLocation = new byte[wideCharLocationLength*2]; + System.arraycopy(recurrencePattern, offset, wideCharLocation, 0, wideCharLocation.length); + offset += wideCharLocation.length; + } + + // See http://msdn.microsoft.com/en-us/library/cc979209(office.12).aspx + if ( (overrideFlags & (ARO_SUBJECT|ARO_LOCATION)) != 0 ) { + ReservedBlockEESize = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4 + ReservedBlockEESize; + } + + extendedLength = offset - initialOffset; + } + + public void setEmbeddedMessage(PSTAppointment embeddedMessage) { + this.embeddedMessage = embeddedMessage; + } + + private int writerVersion2; + private int startDateTime; + private int endDateTime; + private int originalStartDate; + private short overrideFlags; + private byte[] Subject = null; + private int meetingType; + private int ReminderDelta; + private boolean ReminderSet; + private byte[] location = null; + private int busyStatus; + private int attachment; + private boolean subType; +// private int AppointmentColor; // Reserved - don't read from the PST file + @SuppressWarnings("unused") + private int changeHighlightValue; + private int wideCharSubjectLength = 0; + private byte[] wideCharSubject = null; + private int wideCharLocationLength = 0; + private byte[] wideCharLocation = null; + private PSTAppointment embeddedMessage = null; + private PSTAppointment appt; + private int length; + private int extendedLength; + + + // Length of this ExceptionInfo structure in the PST file + public int getLength() { + return length; + } + + // Length of this ExtendedException structure in the PST file + public int getExtendedLength() { + return extendedLength; + } + + + static final short ARO_SUBJECT = 0x0001; + static final short ARO_MEETINGTYPE = 0x0002; + static final short ARO_REMINDERDELTA = 0x0004; + static final short ARO_REMINDER = 0x0008; + static final short ARO_LOCATION = 0x0010; + static final short ARO_BUSYSTATUS = 0x0020; + static final short ARO_ATTACHMENT = 0x0040; + static final short ARO_SUBTYPE = 0x0080; +} diff --git a/com/pff/exceptions/PSTException.java b/com/pff/exceptions/PSTException.java new file mode 100644 index 0000000..b573ced --- /dev/null +++ b/com/pff/exceptions/PSTException.java @@ -0,0 +1,53 @@ +/** + * 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.exceptions; + +/** + * Simple exception for PST File related errors + * @author Richard Johnson + */ +public class PSTException extends Exception +{ + /** + * eclipse generated serial UID + */ + private static final long serialVersionUID = 4284698344354718143L; + + public PSTException(String error) { + super(error); + } + public PSTException(String error, Exception orig) { + super(error, orig); + } +} diff --git a/com/pff/objects/PSTActivity.java b/com/pff/objects/PSTActivity.java new file mode 100644 index 0000000..460bf2a --- /dev/null +++ b/com/pff/objects/PSTActivity.java @@ -0,0 +1,151 @@ +/** + * 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.objects; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; + +import com.pff.exceptions.PSTException; +import com.pff.parsing.DescriptorIndexNode; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.tables.PSTTableBC; +import com.pff.source.PSTSource; + +/** + * PSTActivity represents Journal entries + * @author Richard Johnson + */ +public class PSTActivity extends PSTMessage { + + /** + * @param theFile + * @param descriptorIndexNode + * @throws PSTException + * @throws IOException + */ + public PSTActivity(PSTSource theFile, DescriptorIndexNode descriptorIndexNode) + throws PSTException, IOException { + super(theFile, descriptorIndexNode); + } + + /** + * @param theFile + * @param folderIndexNode + * @param table + * @param localDescriptorItems + */ + public PSTActivity(PSTSource theFile, DescriptorIndexNode folderIndexNode, + PSTTableBC table, + HashMap localDescriptorItems) { + super(theFile, folderIndexNode, table, localDescriptorItems); + } + + /** + * Type + */ + public String getLogType() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008700, PSTSource.PSETID_Log)); + } + /** + * Start + */ + public Date getLogStart() { + return getDateItem(pstFile.getNameToIdMapItem(0x00008706, PSTSource.PSETID_Log)); + } + /** + * Duration + */ + public int getLogDuration() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008707, PSTSource.PSETID_Log)); + } + /** + * End + */ + public Date getLogEnd() { + return getDateItem(pstFile.getNameToIdMapItem(0x00008708, PSTSource.PSETID_Log)); + } + /** + * LogFlags + */ + public int getLogFlags() { + return getIntItem(pstFile.getNameToIdMapItem(0x0000870c, PSTSource.PSETID_Log)); + } + /** + * DocPrinted + */ + public boolean isDocumentPrinted() { + return (getBooleanItem(pstFile.getNameToIdMapItem(0x0000870e, PSTSource.PSETID_Log))); + } + /** + * DocSaved + */ + public boolean isDocumentSaved() { + return (getBooleanItem(pstFile.getNameToIdMapItem(0x0000870f, PSTSource.PSETID_Log))); + } + /** + * DocRouted + */ + public boolean isDocumentRouted() { + return (getBooleanItem(pstFile.getNameToIdMapItem(0x00008710, PSTSource.PSETID_Log))); + } + /** + * DocPosted + */ + public boolean isDocumentPosted() { + return (getBooleanItem(pstFile.getNameToIdMapItem(0x00008711, PSTSource.PSETID_Log))); + } + /** + * Type Description + */ + public String getLogTypeDesc() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008712, PSTSource.PSETID_Log)); + } + + public String toString() { + return + "Type ASCII or Unicode string: "+ getLogType() + "\n" + + "Start Filetime: "+ getLogStart() + "\n" + + "Duration Integer 32-bit signed: "+ getLogDuration() + "\n" + + "End Filetime: "+ getLogEnd() + "\n" + + "LogFlags Integer 32-bit signed: "+ getLogFlags() + "\n" + + "DocPrinted Boolean: "+ isDocumentPrinted() + "\n" + + "DocSaved Boolean: "+ isDocumentSaved() + "\n" + + "DocRouted Boolean: "+ isDocumentRouted() + "\n" + + "DocPosted Boolean: "+ isDocumentPosted() + "\n" + + "TypeDescription ASCII or Unicode string: "+ getLogTypeDesc(); + + } + +} diff --git a/com/pff/objects/PSTAppointment.java b/com/pff/objects/PSTAppointment.java new file mode 100644 index 0000000..c1e9f0c --- /dev/null +++ b/com/pff/objects/PSTAppointment.java @@ -0,0 +1,200 @@ +/** + * 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.objects; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; + +import com.pff.exceptions.PSTException; +import com.pff.objects.sub.PSTTimeZone; +import com.pff.parsing.DescriptorIndexNode; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.tables.PSTTableBC; +import com.pff.source.PSTSource; + +/** + * PSTAppointment is for Calendar items + * @author Richard Johnson + */ +public class PSTAppointment extends PSTMessage { + + public PSTAppointment(PSTSource theFile, DescriptorIndexNode descriptorIndexNode) + throws PSTException, IOException + { + super(theFile, descriptorIndexNode); + } + + public PSTAppointment(PSTSource theFile, DescriptorIndexNode folderIndexNode, PSTTableBC table, HashMap localDescriptorItems) + { + super(theFile, folderIndexNode, table, localDescriptorItems); + } + + public boolean getSendAsICAL() { + return (getBooleanItem(pstFile.getNameToIdMapItem(0x00008200, PSTSource.PSETID_Appointment))); + } + public int getBusyStatus() + { + return getIntItem(pstFile.getNameToIdMapItem(0x00008205, PSTSource.PSETID_Appointment)); + } + public boolean getShowAsBusy() { + return getBusyStatus() == 2; + } + public String getLocation() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008208, PSTSource.PSETID_Appointment)); + } + public Date getStartTime() { + return getDateItem(pstFile.getNameToIdMapItem(0x0000820d, PSTSource.PSETID_Appointment)); + } + public PSTTimeZone getStartTimeZone() { + return getTimeZoneItem(pstFile.getNameToIdMapItem(0x0000825e, PSTSource.PSETID_Appointment)); + } + public Date getEndTime() { + return getDateItem(pstFile.getNameToIdMapItem(0x0000820e, PSTSource.PSETID_Appointment)); + } + public PSTTimeZone getEndTimeZone() { + return getTimeZoneItem(pstFile.getNameToIdMapItem(0x0000825f, PSTSource.PSETID_Appointment)); + } + + public PSTTimeZone getRecurrenceTimeZone() { + String desc = getStringItem(pstFile.getNameToIdMapItem(0x00008234, PSTSource.PSETID_Appointment)); + if ( desc!= null && desc.length() != 0 ) { + byte[] tzData = getBinaryItem(pstFile.getNameToIdMapItem(0x00008233, PSTSource.PSETID_Appointment)); + if ( tzData != null && tzData.length != 0 ) { + return new PSTTimeZone(desc, tzData); + } + } + return null; + } + public int getDuration() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008213, PSTSource.PSETID_Appointment)); + } + public int getColor() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008214, PSTSource.PSETID_Appointment)); + } + public boolean getSubType() { + return (getIntItem(pstFile.getNameToIdMapItem(0x00008215, PSTSource.PSETID_Appointment)) != 0); + } + public int getMeetingStatus() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008217, PSTSource.PSETID_Appointment)); + } + public int getResponseStatus() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008218, PSTSource.PSETID_Appointment)); + } + public boolean isRecurring() { + return getBooleanItem(pstFile.getNameToIdMapItem(0x00008223, PSTSource.PSETID_Appointment)); + } + public Date getRecurrenceBase() { + return getDateItem(pstFile.getNameToIdMapItem(0x00008228, PSTSource.PSETID_Appointment)); + } + public int getRecurrenceType() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008231, PSTSource.PSETID_Appointment)); + } + public String getRecurrencePattern() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008232, PSTSource.PSETID_Appointment)); + } + public byte[] getRecurrenceStructure() { + return getBinaryItem(pstFile.getNameToIdMapItem(0x00008216, PSTSource.PSETID_Appointment)); + } + public byte[] getTimezone() { + return getBinaryItem(pstFile.getNameToIdMapItem(0x00008233, PSTSource.PSETID_Appointment)); + } + public String getAllAttendees() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008238, PSTSource.PSETID_Appointment)); + } + public String getToAttendees() { + return getStringItem(pstFile.getNameToIdMapItem(0x0000823b, PSTSource.PSETID_Appointment)); + } + public String getCCAttendees() { + return getStringItem(pstFile.getNameToIdMapItem(0x0000823c, PSTSource.PSETID_Appointment)); + } + public int getAppointmentSequence() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008201, PSTSource.PSETID_Appointment)); + } + + // online meeting properties + public boolean isOnlineMeeting() { + return (getBooleanItem(pstFile.getNameToIdMapItem(0x00008240, PSTSource.PSETID_Appointment))); + } + public int getNetMeetingType() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008241, PSTSource.PSETID_Appointment)); + } + public String getNetMeetingServer() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008242, PSTSource.PSETID_Appointment)); + } + public String getNetMeetingOrganizerAlias() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008243, PSTSource.PSETID_Appointment)); + } + public boolean getNetMeetingAutostart() { + return (getIntItem(pstFile.getNameToIdMapItem(0x00008245, PSTSource.PSETID_Appointment)) != 0); + } + public boolean getConferenceServerAllowExternal() { + return (getBooleanItem(pstFile.getNameToIdMapItem(0x00008246, PSTSource.PSETID_Appointment))); + } + public String getNetMeetingDocumentPathName() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008247, PSTSource.PSETID_Appointment)); + } + public String getNetShowURL() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008248, PSTSource.PSETID_Appointment)); + } + public Date getAttendeeCriticalChange() { + return getDateItem(pstFile.getNameToIdMapItem(0x00000001, PSTSource.PSETID_Meeting)); + } + public Date getOwnerCriticalChange() { + return getDateItem(pstFile.getNameToIdMapItem(0x0000001a, PSTSource.PSETID_Meeting)); + } + public String getConferenceServerPassword() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008249, PSTSource.PSETID_Appointment)); + } + + public boolean getAppointmentCounterProposal() { + return (getBooleanItem(pstFile.getNameToIdMapItem(0x00008257, PSTSource.PSETID_Appointment))); + } + + public boolean isSilent() { + return (getBooleanItem(pstFile.getNameToIdMapItem(0x00000004, PSTSource.PSETID_Meeting))); + } + + public String getRequiredAttendees() { + return getStringItem(this.pstFile.getNameToIdMapItem(0x00000006, PSTSource.PSETID_Meeting)); + } + + public int getLocaleId() { + return getIntItem(0x3ff1); + } + + public byte[] getGlobalObjectId() { + return getBinaryItem(pstFile.getNameToIdMapItem(0x00000003, PSTSource.PSETID_Meeting)); + } +} diff --git a/com/pff/objects/PSTAttachment.java b/com/pff/objects/PSTAttachment.java new file mode 100644 index 0000000..3e03643 --- /dev/null +++ b/com/pff/objects/PSTAttachment.java @@ -0,0 +1,263 @@ +/** + * 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.objects; + +import java.io.*; +import java.util.*; + +import com.pff.PSTUtils; +import com.pff.exceptions.PSTException; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.PSTNodeInputStream; +import com.pff.parsing.tables.PSTTableBC; +import com.pff.parsing.tables.PSTTableBCItem; +import com.pff.source.PSTSource; + + +/** + * Class containing attachment information + * @author Richard Johnson + */ +public class PSTAttachment extends PSTObject { + + PSTAttachment(PSTSource theFile, PSTTableBC table, HashMap localDescriptorItems) { + super(theFile, null, table, localDescriptorItems); + } + + public int getSize() { + return this.getIntItem(0x0e20); + } + + public Date getCreationTime() { + return this.getDateItem(0x3007); + } + + public Date getModificationTime() { + return this.getDateItem(0x3008); + } + + public PSTMessage getEmbeddedPSTMessage() + throws IOException, PSTException + { + PSTNodeInputStream in = null; + if ( getIntItem(0x3705) == PSTAttachment.ATTACHMENT_METHOD_EMBEDDED ) { + PSTTableBCItem item = items.get(0x3701); + if ( item.entryValueType == 0x0102 ) { + if ( !item.isExternalValueReference ) + { + in = new PSTNodeInputStream(this.pstFile, item.data); + } else { + // We are in trouble! + throw new PSTException("External reference in getEmbeddedPSTMessage()!\n"); + } + } else if ( item.entryValueType == 0x000D ) { + int descriptorItem = (int)PSTUtils.convertLittleEndianBytesToLong(item.data, 0, 4); + //PSTObject.printHexFormatted(item.data, true); + PSTDescriptorItem descriptorItemNested = this.localDescriptorItems.get(descriptorItem); + in = new PSTNodeInputStream(this.pstFile, descriptorItemNested); + this.localDescriptorItems.putAll(pstFile.getPSTDescriptorItems(descriptorItemNested.getSubNodeOffsetIndexIdentifier())); + /* + if ( descriptorItemNested != null ) { + try { + data = descriptorItemNested.getData(); + blockOffsets = descriptorItemNested.getBlockOffsets(); + } catch (Exception e) { + e.printStackTrace(); + + data = null; + blockOffsets = null; + } + } + * + */ + } + + if ( in == null ) { + return null; + } + + try { + PSTTableBC attachmentTable = new PSTTableBC(in); + return PSTObject.createAppropriatePSTMessageObject(pstFile, this.descriptorIndexNode, attachmentTable, localDescriptorItems); + } catch ( PSTException e ) { + e.printStackTrace(); + } + return null; + } + return null; + } + + public InputStream getFileInputStream() + throws IOException, PSTException + { + + PSTTableBCItem attachmentDataObject = items.get(0x3701); + + if (attachmentDataObject.isExternalValueReference) { + PSTDescriptorItem descriptorItemNested = this.localDescriptorItems.get(attachmentDataObject.entryValueReference); + return new PSTNodeInputStream(this.pstFile, descriptorItemNested); + } else { + // internal value references are never encrypted + return new PSTNodeInputStream(this.pstFile, attachmentDataObject.data, false); + } + + } + + public int getFilesize() + throws PSTException, IOException + { + PSTTableBCItem attachmentDataObject = items.get(0x3701); + if (attachmentDataObject.isExternalValueReference) { + PSTDescriptorItem descriptorItemNested = this.localDescriptorItems.get(attachmentDataObject.entryValueReference); + if (descriptorItemNested == null) { + throw new PSTException("missing attachment descriptor item for: "+attachmentDataObject.entryValueReference); + } + return descriptorItemNested.getDataSize(); + } else { + // raw attachment data, right there! + return attachmentDataObject.data.length; + } + + } + + + // attachment properties + + /** + * Attachment (short) filename ASCII or Unicode string + */ + public String getFilename() { + return this.getStringItem(0x3704); + } + + public static final int ATTACHMENT_METHOD_NONE = 0; + public static final int ATTACHMENT_METHOD_BY_VALUE = 1; + public static final int ATTACHMENT_METHOD_BY_REFERENCE = 2; + public static final int ATTACHMENT_METHOD_BY_REFERENCE_RESOLVE = 3; + public static final int ATTACHMENT_METHOD_BY_REFERENCE_ONLY = 4; + public static final int ATTACHMENT_METHOD_EMBEDDED = 5; + public static final int ATTACHMENT_METHOD_OLE = 6; + + /** + * Attachment method Integer 32-bit signed 0 => None (No attachment) 1 => By value 2 => By reference 3 => By reference resolve 4 => By reference only 5 => Embedded message 6 => OLE + */ + public int getAttachMethod() { + return this.getIntItem(0x3705); + } + /** + * Attachment size + */ + public int getAttachSize() { + return this.getIntItem(0x0e20); + } + /** + * Attachment number + */ + public int getAttachNum() { + return this.getIntItem(0x0e21); + } + /** + * Attachment long filename ASCII or Unicode string + */ + public String getLongFilename() { + return this.getStringItem(0x3707); + } + /** + * Attachment (short) pathname ASCII or Unicode string + */ + public String getPathname() { + return this.getStringItem(0x3708); + } + /** + * Attachment Position Integer 32-bit signed + */ + public int getRenderingPosition() { + return this.getIntItem(0x370b); + } + /** + * Attachment long pathname ASCII or Unicode string + */ + public String getLongPathname() { + return this.getStringItem(0x370d); + } + /** + * Attachment mime type ASCII or Unicode string + */ + public String getMimeTag() { + return this.getStringItem(0x370e); + } + /** + * Attachment mime sequence + */ + public int getMimeSequence() { + return this.getIntItem(0x3710); + } + + /** + * Attachment Content ID + */ + public String getContentId() { + return this.getStringItem(0x3712); + } + + /** + * Attachment not available in HTML + */ + public boolean isAttachmentInvisibleInHtml() { + int actionFlag = this.getIntItem(0x3714); + return ((actionFlag & 0x1) > 0); + } + /** + * Attachment not available in RTF + */ + public boolean isAttachmentInvisibleInRTF() { + int actionFlag = this.getIntItem(0x3714); + return ((actionFlag & 0x2) > 0); + } + /** + * Attachment is MHTML REF + */ + public boolean isAttachmentMhtmlRef() { + int actionFlag = this.getIntItem(0x3714); + return ((actionFlag & 0x4) > 0); + } + + /** + * Attachment content disposition + */ + public String getAttachmentContentDisposition() { + return this.getStringItem(0x3716); + } + +} diff --git a/com/pff/objects/PSTContact.java b/com/pff/objects/PSTContact.java new file mode 100644 index 0000000..a3170cd --- /dev/null +++ b/com/pff/objects/PSTContact.java @@ -0,0 +1,870 @@ +/** + * 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.objects; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; + +import com.pff.exceptions.PSTException; +import com.pff.parsing.DescriptorIndexNode; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.tables.PSTTableBC; +import com.pff.source.PSTSource; + + +/** + * Class for Contacts + * @author Richard Johnson + */ +public class PSTContact extends PSTMessage { + + /** + * @param theFile + * @param descriptorIndexNode + * @throws PSTException + * @throws IOException + */ + public PSTContact(PSTSource theFile, DescriptorIndexNode descriptorIndexNode) + throws PSTException, IOException { + super(theFile, descriptorIndexNode); + } + + /** + * @param theFile + * @param folderIndexNode + * @param table + * @param localDescriptorItems + */ + public PSTContact(PSTSource theFile, DescriptorIndexNode folderIndexNode, PSTTableBC table, HashMap localDescriptorItems) { + super(theFile, folderIndexNode, table, localDescriptorItems); + } + + + /** + * Contact's Account name + */ + public String getAccount() { + return this.getStringItem(0x3a00); + } + /** + * Callback telephone number + */ + public String getCallbackTelephoneNumber() { + return this.getStringItem(0x3a02); + } + /** + * Contact's generational abbreviation FTK: Name suffix + */ + public String getGeneration() { + return this.getStringItem(0x3a05); + } + /** + * Contacts given name + */ + public String getGivenName() { + return this.getStringItem(0x3a06); + } + /** + * Contacts Government ID Number + */ + public String getGovernmentIdNumber() { + return this.getStringItem(0x3a07); + } + + /** + * Business/Office Telephone Number + */ + public String getBusinessTelephoneNumber() { + return this.getStringItem(0x3a08); + } + /** + * Home Telephone Number + */ + public String getHomeTelephoneNumber() { + return this.getStringItem(0x3a09); + } + /** + * Contacts initials + */ + public String getInitials() { + return this.getStringItem(0x3a0a); + } + /** + * Keyword + */ + public String getKeyword() { + return this.getStringItem(0x3a0b); + } + /** + * Contact's language + */ + public String getLanguage() { + return this.getStringItem(0x3a0c); + } + /** + * Contact's location + */ + public String getLocation() { + return this.getStringItem(0x3a0d); + } + /** + * MHS Common Name + */ + public String getMhsCommonName() { + return this.getStringItem(0x3a0f); + } + /** + * Organizational identification number + */ + public String getOrganizationalIdNumber() { + return this.getStringItem(0x3a10); + } + /** + * Contact's surname FTK: Last name + */ + public String getSurname() { + return this.getStringItem(0x3a11); + } + /** + * Original display name + */ + public String getOriginalDisplayName() { + return this.getStringItem(0x3a13); + } + /** + * Default Postal Address + */ + public String getPostalAddress() { + return this.getStringItem(0x3a15); + } + /** + * Contact's company name + */ + public String getCompanyName() { + return this.getStringItem(0x3a16); + } + /** + * Contact's job title FTK: Profession + */ + public String getTitle() { + return this.getStringItem(0x3a17); + } + /** + * Contact's department name Used in contact item + */ + public String getDepartmentName() { + return this.getStringItem(0x3a18); + } + /** + * Contact's office location + */ + public String getOfficeLocation() { + return this.getStringItem(0x3a19); + } + /** + * Primary Telephone + */ + public String getPrimaryTelephoneNumber() { + return this.getStringItem(0x3a1a); + } + + /** + * Contact's secondary office (business) phone number + */ + public String getBusiness2TelephoneNumber() { + return this.getStringItem(0x3a1b); + } + + /** + * Mobile Phone Number + */ + public String getMobileTelephoneNumber() { + return this.getStringItem(0x3a1c); + } + /** + * Radio Phone Number + */ + public String getRadioTelephoneNumber() { + return this.getStringItem(0x3a1d); + } + /** + * Car Phone Number + */ + public String getCarTelephoneNumber() { + return this.getStringItem(0x3a1e); + } + /** + * Other Phone Number + */ + public String getOtherTelephoneNumber() { + return this.getStringItem(0x3a1f); + } + /** + * Transmittable display name + */ + public String getTransmittableDisplayName() { + return this.getStringItem(0x3a20); + } + /** + * Pager Phone Number + */ + public String getPagerTelephoneNumber() { + return this.getStringItem(0x3a21); + } + /** + * Primary Fax Number + */ + public String getPrimaryFaxNumber() { + return this.getStringItem(0x3a23); + } + + /** + * Contact's office (business) fax number + */ + public String getBusinessFaxNumber() { + return this.getStringItem(0x3a24); + } + /** + * Contact's home fax number + */ + public String getHomeFaxNumber() { + return this.getStringItem(0x3a25); + } + + /** + * Business Address Country + */ + public String getBusinessAddressCountry() { + return this.getStringItem(0x3a26); + } + /** + * Business Address City + */ + public String getBusinessAddressCity() { + return this.getStringItem(0x3a27); + } + /** + * Business Address State + */ + public String getBusinessAddressStateOrProvince () { + return this.getStringItem(0x3a28); + } + /** + * Business Address Street + */ + public String getBusinessAddressStreet() { + return this.getStringItem(0x3a29); + } + /** + * Business Postal Code + */ + public String getBusinessPostalCode() { + return this.getStringItem(0x3a2a); + } + /** + * Business PO Box + */ + public String getBusinessPoBox() { + return this.getStringItem(0x3a2b); + } + /** + * Telex Number + */ + public String getTelexNumber() { + return this.getStringItem(0x3a2c); + } + /** + * ISDN Number + */ + public String getIsdnNumber() { + return this.getStringItem(0x3a2d); + } + /** + * Assistant Phone Number + */ + public String getAssistantTelephoneNumber() { + return this.getStringItem(0x3a2e); + } + /** + * Home Phone 2 + */ + public String getHome2TelephoneNumber() { + return this.getStringItem(0x3a2f); + } + /** + * Assistant�s Name + */ + public String getAssistant() { + return this.getStringItem(0x3a30); + } + /** + * Hobbies + */ + public String getHobbies() { + return this.getStringItem(0x3a43); + } + /** + * Middle Name + */ + public String getMiddleName() { + return this.getStringItem(0x3a44); + } + /** + * Display Name Prefix (Contact Title) + */ + public String getDisplayNamePrefix() { + return this.getStringItem(0x3a45); + } + /** + * Profession + */ + public String getProfession() { + return this.getStringItem(0x3a46); + } + /** + * Preferred By Name + */ + public String getPreferredByName() { + return this.getStringItem(0x3a47); + } + /** + * Spouse�s Name + */ + public String getSpouseName() { + return this.getStringItem(0x3a48); + } + /** + * Computer Network Name + */ + public String getComputerNetworkName() { + return this.getStringItem(0x3a49); + } + /** + * Customer ID + */ + public String getCustomerId() { + return this.getStringItem(0x3a4a); + } + /** + * TTY/TDD Phone + */ + public String getTtytddPhoneNumber() { + return this.getStringItem(0x3a4b); + } + /** + * Ftp Site + */ + public String getFtpSite() { + return this.getStringItem(0x3a4c); + } + /** + * Manager�s Name + */ + public String getManagerName() { + return this.getStringItem(0x3a4e); + } + /** + * Nickname + */ + public String getNickname() { + return this.getStringItem(0x3a4f); + } + /** + * Personal Home Page + */ + public String getPersonalHomePage() { + return this.getStringItem(0x3a50); + } + /** + * Business Home Page + */ + public String getBusinessHomePage() { + return this.getStringItem(0x3a51); + } + + /** + * Note + */ + public String getNote() { + return this.getStringItem(0x6619); + } + + String getNamedStringItem(int key) { + int id = pstFile.getNameToIdMapItem(key, PSTSource.PSETID_Address); + if ( id != -1 ) { + return getStringItem(id); + } + return ""; + } + + public String getSMTPAddress() + { + return getNamedStringItem(0x00008084); + } + /** + * Company Main Phone + */ + public String getCompanyMainPhoneNumber() { + return getStringItem(0x3a57); + } + /** + * Children's names + */ + public String getChildrensNames() { + return this.getStringItem(0x3a58); + } + /** + * Home Address City + */ + public String getHomeAddressCity() { + return this.getStringItem(0x3a59); + } + /** + * Home Address Country + */ + public String getHomeAddressCountry() { + return this.getStringItem(0x3a5a); + } + /** + * Home Address Postal Code + */ + public String getHomeAddressPostalCode() { + return this.getStringItem(0x3a5b); + } + /** + * Home Address State or Province + */ + public String getHomeAddressStateOrProvince () { + return this.getStringItem(0x3a5c); + } + /** + * Home Address Street + */ + public String getHomeAddressStreet() { + return this.getStringItem(0x3a5d); + } + /** + * Home Address Post Office Box + */ + public String getHomeAddressPostOfficeBox () { + return this.getStringItem(0x3a5e); + } + /** + * Other Address City + */ + public String getOtherAddressCity() { + return this.getStringItem(0x3a5f); + } + /** + * Other Address Country + */ + public String getOtherAddressCountry() { + return this.getStringItem(0x3a60); + } + /** + * Other Address Postal Code + */ + public String getOtherAddressPostalCode() { + return this.getStringItem(0x3a61); + } + /** + * Other Address State + */ + public String getOtherAddressStateOrProvince () { + return this.getStringItem(0x3a62); + } + /** + * Other Address Street + */ + public String getOtherAddressStreet() { + return this.getStringItem(0x3a63); + } + /** + * Other Address Post Office box + */ + public String getOtherAddressPostOfficeBox() { + return this.getStringItem(0x3a64); + } + + /////////////////////////////////////////////////// + // Below are the values from the name to id map... + /////////////////////////////////////////////////// + + /** + * File under FTK: File as + */ + public String getFileUnder() { + return getNamedStringItem(0x00008005); + } + + /** + * Home Address + */ + public String getHomeAddress() { + return getNamedStringItem(0x0000801a); + } + + /** + * Business Address + */ + public String getWorkAddress() { + return getNamedStringItem(0x0000801b); + } + + /** + * Other Address + */ + public String getOtherAddress() { + return getNamedStringItem(0x0000801c); + } + + /** + * Selected Mailing Address + */ + public int getPostalAddressId() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008022, PSTSource.PSETID_Address)); + } + + /** + * Webpage + */ + public String getHtml() { + return getNamedStringItem(0x0000802b); + } + + /** + * Business Address City + */ + public String getWorkAddressStreet() { + return getNamedStringItem(0x00008045); + } + + /** + * Business Address Street + */ + public String getWorkAddressCity() { + return getNamedStringItem(0x00008046); + } + + /** + * Business Address State + */ + public String getWorkAddressState() { + return getNamedStringItem(0x00008047); + } + + /** + * Business Address Postal Code + */ + public String getWorkAddressPostalCode() { + return getNamedStringItem(0x00008048); + } + + /** + * Business Address Country + */ + public String getWorkAddressCountry() { + return getNamedStringItem(0x00008049); + } + + /** + * Business Address Country + */ + public String getWorkAddressPostOfficeBox() { + return getNamedStringItem(0x0000804A); + } + + /** + * IM Address + */ + public String getInstantMessagingAddress() { + return getNamedStringItem(0x00008062); + } + + /** + * E-mail1 Display Name + */ + public String getEmail1DisplayName() { + return getNamedStringItem(0x00008080); + } + + /** + * E-mail1 Address Type + */ + public String getEmail1AddressType() { + return getNamedStringItem(0x00008082); + } + + /** + * E-mail1 Address + */ + public String getEmail1EmailAddress() { + return getNamedStringItem(0x00008083); + } + + /** + * E-mail1 Display Name + */ + public String getEmail1OriginalDisplayName() { + return getNamedStringItem(0x00008084); + } + + /** + * E-mail1 type + */ + public String getEmail1EmailType() { + return getNamedStringItem(0x00008087); + } + + /** + * E-mail2 display name + */ + public String getEmail2DisplayName() { + return getNamedStringItem(0x00008090); + } + + /** + * E-mail2 address type + */ + public String getEmail2AddressType() { + return getNamedStringItem(0x00008092); + } + + /** + * E-mail2 e-mail address + */ + public String getEmail2EmailAddress() { + return getNamedStringItem(0x00008093); + } + + /** + * E-mail2 original display name + */ + public String getEmail2OriginalDisplayName() { + return getNamedStringItem(0x00008094); + } + + /** + * E-mail3 display name + */ + public String getEmail3DisplayName() { + return getNamedStringItem(0x000080a0); + } + + /** + * E-mail3 address type + */ + public String getEmail3AddressType() { + return getNamedStringItem(0x000080a2); + } + + /** + * E-mail3 e-mail address + */ + public String getEmail3EmailAddress() { + return getNamedStringItem(0x000080a3); + } + + /** + * E-mail3 original display name + */ + public String getEmail3OriginalDisplayName() { + return getNamedStringItem(0x000080a4); + } + + /** + * Fax1 Address Type + */ + public String getFax1AddressType() { + return getNamedStringItem(0x000080b2); + } + + /** + * Fax1 Email Address + */ + public String getFax1EmailAddress() { + return getNamedStringItem(0x000080b3); + } + + /** + * Fax1 Original Display Name + */ + public String getFax1OriginalDisplayName() { + return getNamedStringItem(0x000080b4); + } + + /** + * Fax2 Address Type + */ + public String getFax2AddressType() { + return getNamedStringItem(0x000080c2); + } + + /** + * Fax2 Email Address + */ + public String getFax2EmailAddress() { + return getNamedStringItem(0x000080c3); + } + + /** + * Fax2 Original Display Name + */ + public String getFax2OriginalDisplayName() { + return getNamedStringItem(0x000080c4); + } + + /** + * Fax3 Address Type + */ + public String getFax3AddressType() { + return getNamedStringItem(0x000080d2); + } + + /** + * Fax3 Email Address + */ + public String getFax3EmailAddress() { + return getNamedStringItem(0x000080d3); + } + + /** + * Fax3 Original Display Name + */ + public String getFax3OriginalDisplayName() { + return getNamedStringItem(0x000080d4); + } + + /** + * Free/Busy Location (URL) + */ + public String getFreeBusyLocation() { + return getNamedStringItem(0x000080d8); + } + + /** + * Birthday + */ + public Date getBirthday() { + return this.getDateItem(0x3a42); + } + + /** + * (Wedding) Anniversary + */ + public Date getAnniversary() { + return this.getDateItem(0x3a41); + } + + public String toString() { + + return + "Contact's Account name: "+getAccount()+"\n"+ + "Display Name: "+getGivenName()+" "+getSurname()+" ("+getSMTPAddress()+")\n"+ + "Email1 Address Type: "+getEmail1AddressType()+"\n"+ + "Email1 Address: "+getEmail1EmailAddress()+"\n"+ + "Callback telephone number: "+getCallbackTelephoneNumber()+"\n"+ + "Contact's generational abbreviation (name suffix): "+getGeneration()+"\n"+ + "Contacts given name: "+getGivenName()+"\n"+ + "Contacts Government ID Number: "+getGovernmentIdNumber()+"\n"+ + "Business/Office Telephone Number: "+getBusinessTelephoneNumber()+"\n"+ + "Home Telephone Number: "+getHomeTelephoneNumber()+"\n"+ + "Contacts initials: "+getInitials()+"\n"+ + "Keyword: "+getKeyword()+"\n"+ + "Contact's language: "+getLanguage()+"\n"+ + "Contact's location: "+getLocation()+"\n"+ + "MHS Common Name: "+getMhsCommonName()+"\n"+ + "Organizational identification number: "+getOrganizationalIdNumber()+"\n"+ + "Contact's surname (Last name): "+getSurname()+"\n"+ + "Original display name: "+getOriginalDisplayName()+"\n"+ + "Default Postal Address: "+getPostalAddress()+"\n"+ + "Contact's company name: "+getCompanyName()+"\n"+ + "Contact's job title (Profession): "+getTitle()+"\n"+ + "Contact's department name Used in contact ite: "+getDepartmentName()+"\n"+ + "Contact's office location: "+getOfficeLocation()+"\n"+ + "Primary Telephone: "+getPrimaryTelephoneNumber()+"\n"+ + "Contact's secondary office (business) phone number: "+getBusiness2TelephoneNumber()+"\n"+ + "Mobile Phone Number: "+getMobileTelephoneNumber()+"\n"+ + "Radio Phone Number: "+getRadioTelephoneNumber()+"\n"+ + "Car Phone Number: "+getCarTelephoneNumber()+"\n"+ + "Other Phone Number: "+getOtherTelephoneNumber()+"\n"+ + "Transmittable display name: "+getTransmittableDisplayName()+"\n"+ + "Pager Phone Number: "+getPagerTelephoneNumber()+"\n"+ + "Primary Fax Number: "+getPrimaryFaxNumber()+"\n"+ + "Contact's office (business) fax numbe: "+getBusinessFaxNumber()+"\n"+ + "Contact's home fax number: "+getHomeFaxNumber()+"\n"+ + "Business Address Country: "+getBusinessAddressCountry()+"\n"+ + "Business Address City: "+getBusinessAddressCity()+"\n"+ + "Business Address State: "+getBusinessAddressStateOrProvince ()+"\n"+ + "Business Address Street: "+getBusinessAddressStreet()+"\n"+ + "Business Postal Code: "+getBusinessPostalCode()+"\n"+ + "Business PO Box: "+getBusinessPoBox()+"\n"+ + "Telex Number: "+getTelexNumber()+"\n"+ + "ISDN Number: "+getIsdnNumber()+"\n"+ + "Assistant Phone Number: "+getAssistantTelephoneNumber()+"\n"+ + "Home Phone 2: "+getHome2TelephoneNumber()+"\n"+ + "Assistant's Name: "+getAssistant()+"\n"+ + "Hobbies: "+getHobbies()+"\n"+ + "Middle Name: "+getMiddleName()+"\n"+ + "Display Name Prefix (Contact Title): "+getDisplayNamePrefix()+"\n"+ + "Profession: "+getProfession()+"\n"+ + "Preferred By Name: "+getPreferredByName()+"\n"+ + "Spouse�s Name: "+getSpouseName()+"\n"+ + "Computer Network Name: "+getComputerNetworkName()+"\n"+ + "Customer ID: "+getCustomerId()+"\n"+ + "TTY/TDD Phone: "+getTtytddPhoneNumber()+"\n"+ + "Ftp Site: "+getFtpSite()+"\n"+ + "Manager's Name: "+getManagerName()+"\n"+ + "Nickname: "+getNickname()+"\n"+ + "Personal Home Page: "+getPersonalHomePage()+"\n"+ + "Business Home Page: "+getBusinessHomePage()+"\n"+ + "Company Main Phone: "+getCompanyMainPhoneNumber()+"\n"+ + "Childrens names: "+getChildrensNames()+"\n"+ + "Home Address City: "+getHomeAddressCity()+"\n"+ + "Home Address Country: "+getHomeAddressCountry()+"\n"+ + "Home Address Postal Code: "+getHomeAddressPostalCode()+"\n"+ + "Home Address State or Province: "+getHomeAddressStateOrProvince ()+"\n"+ + "Home Address Street: "+getHomeAddressStreet()+"\n"+ + "Home Address Post Office Box: "+getHomeAddressPostOfficeBox ()+"\n"+ + "Other Address City: "+getOtherAddressCity()+"\n"+ + "Other Address Country: "+getOtherAddressCountry()+"\n"+ + "Other Address Postal Code: "+getOtherAddressPostalCode()+"\n"+ + "Other Address State: "+getOtherAddressStateOrProvince ()+"\n"+ + "Other Address Street: "+getOtherAddressStreet()+"\n"+ + "Other Address Post Office box: "+getOtherAddressPostOfficeBox()+"\n" + + "\n"+ + this.getBody(); + } +} diff --git a/com/pff/objects/PSTFolder.java b/com/pff/objects/PSTFolder.java new file mode 100644 index 0000000..68d00ff --- /dev/null +++ b/com/pff/objects/PSTFolder.java @@ -0,0 +1,420 @@ +/** + * 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.objects; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + + +import com.pff.exceptions.PSTException; +import com.pff.parsing.DescriptorIndexNode; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.PSTNodeInputStream; +import com.pff.parsing.tables.PSTTable7C; +import com.pff.parsing.tables.PSTTable7CItem; +import com.pff.parsing.tables.PSTTableBC; +import com.pff.source.PSTSource; + + +/** + * Represents a folder in the PST File + * Allows you to access child folders or items. Items are accessed through a sort of cursor arrangement. + * This allows for incremental reading of a folder which may have _lots_ of emails. + * @author Richard Johnson + */ +public class PSTFolder extends PSTObject { + + /** + * a constructor for the rest of us... + * @param theFile + * @param descriptorIndexNode + * @throws PSTException + * @throws IOException + */ + public PSTFolder(PSTSource theFile, DescriptorIndexNode descriptorIndexNode) + throws PSTException, IOException + { + super(theFile, descriptorIndexNode); + } + + /** + * For pre-populating a folder object with values. + * Not recommended for use outside this library + * @param theFile + * @param folderIndexNode + * @param table + */ + PSTFolder(PSTSource theFile, DescriptorIndexNode folderIndexNode, PSTTableBC table, HashMap localDescriptorItems) { + super(theFile, folderIndexNode, table, localDescriptorItems); + } + + /** + * get all of the sub folders... + * there are not usually thousands, so we just do it in one big operation. + * @return all of the subfolders + * @throws PSTException + * @throws IOException + */ + public ArrayList getSubFolders() + throws PSTException, IOException + { + initSubfoldersTable(); + ArrayList output = new ArrayList(); + if (this.hasSubfolders()) { + try { + List> itemMapSet = subfoldersTable.getItems(); + for (HashMap itemMap : itemMapSet) { + PSTTable7CItem item = itemMap.get(26610); + PSTFolder folder = (PSTFolder)PSTObject.detectAndLoadPSTObject(pstFile, item.entryValueReference); + output.add(folder); + } + } catch (PSTException err) { + // hierachy node doesn't exist + throw new PSTException("Can't get child folders for folder "+this.getDisplayName()+"("+this.getDescriptorNodeId()+") child count: "+this.getContentCount()+ " - "+err.toString()); + } + } + // try and get subfolders? + return output; + } + + private void initSubfoldersTable() + throws IOException, PSTException + { + if (subfoldersTable != null) { + return; + } + + if (this.hasSubfolders()) { + long folderDescriptorIndex = this.descriptorIndexNode.descriptorIdentifier + 11; + try { + DescriptorIndexNode folderDescriptor = this.pstFile.getDescriptorIndexNode(folderDescriptorIndex); + HashMap tmp = null; + if (folderDescriptor.localDescriptorsOffsetIndexIdentifier > 0) { + //tmp = new PSTDescriptor(pstFile, folderDescriptor.localDescriptorsOffsetIndexIdentifier).getChildren(); + tmp = pstFile.getPSTDescriptorItems(folderDescriptor.localDescriptorsOffsetIndexIdentifier); + } + subfoldersTable = new PSTTable7C(new PSTNodeInputStream(pstFile, pstFile.getOffsetIndexNode(folderDescriptor.dataOffsetIndexIdentifier)), tmp); + } catch (PSTException err) { + // hierachy node doesn't exist + throw new PSTException("Can't get child folders for folder "+this.getDisplayName()+"("+this.getDescriptorNodeId()+") child count: "+this.getContentCount()+ " - "+err.toString()); + } + } + + } + + /** + * internal vars for the tracking of things.. + */ + private int currentEmailIndex = 0; + //private LinkedHashSet otherItems = null; + + private PSTTable7C emailsTable = null; + private LinkedList fallbackEmailsTable = null; + private PSTTable7C subfoldersTable = null; + + /** + * this method goes through all of the children and sorts them into one of the three hash sets. + * @throws PSTException + * @throws IOException + */ + private void initEmailsTable() + throws PSTException, IOException + { + if (this.emailsTable != null || this.fallbackEmailsTable != null) { + return; + } + + // some folder types don't have children: + if (this.getNodeType() == PSTObject.NID_TYPE_SEARCH_FOLDER) { + return; + } + + try { + long folderDescriptorIndex = this.descriptorIndexNode.descriptorIdentifier + 12; // +12 lists emails! :D + DescriptorIndexNode folderDescriptor = this.pstFile.getDescriptorIndexNode(folderDescriptorIndex); + HashMap tmp = null; + if (folderDescriptor.localDescriptorsOffsetIndexIdentifier > 0) { + //tmp = new PSTDescriptor(pstFile, folderDescriptor.localDescriptorsOffsetIndexIdentifier).getChildren(); + tmp = pstFile.getPSTDescriptorItems(folderDescriptor.localDescriptorsOffsetIndexIdentifier); + } + //PSTTable7CForFolder folderDescriptorTable = new PSTTable7CForFolder(folderDescriptor.dataBlock.data, folderDescriptor.dataBlock.blockOffsets,tmp, 0x67F2); + emailsTable = new PSTTable7C( + new PSTNodeInputStream(pstFile, pstFile.getOffsetIndexNode(folderDescriptor.dataOffsetIndexIdentifier)), + tmp, + 0x67F2 + ); + } catch (Exception err) { + + // here we have to attempt to fallback onto the children as listed by the descriptor b-tree + LinkedHashMap> tree = this.pstFile.getChildDescriptorTree(); + + fallbackEmailsTable = new LinkedList(); + LinkedList allChildren = tree.get(this.getDescriptorNode().descriptorIdentifier); + + if (allChildren != null) { + // quickly go through and remove those entries that are not messages! + for (DescriptorIndexNode node : allChildren) { + if (node != null && PSTObject.getNodeType(node.descriptorIdentifier) == PSTObject.NID_TYPE_NORMAL_MESSAGE) { + fallbackEmailsTable.add(node); + } + } + } + + System.err.println( + "Can't get children for folder "+ + this.getDisplayName()+ + "("+this.getDescriptorNodeId()+") child count: "+ + this.getContentCount()+ " - "+ + err.toString()+ ", using alternate child tree with " + fallbackEmailsTable.size()+" items"); + } + } + + /** + * get some children from the folder + * This is implemented as a cursor of sorts, as there could be thousands + * and that is just too many to process at once. + * @param numberToReturn + * @return bunch of children in this folder + * @throws PSTException + * @throws IOException + */ + public ArrayList getChildren(int numberToReturn) + throws PSTException, IOException + { + initEmailsTable(); + + ArrayList output = new ArrayList(); + if (emailsTable != null) { + List> rows = this.emailsTable.getItems(currentEmailIndex, numberToReturn); + + for (int x = 0; x < rows.size(); x++) { + if (this.currentEmailIndex >= this.getContentCount()) + { + // no more! + break; + } + // get the emails from the rows + PSTTable7CItem emailRow = rows.get(x).get(0x67F2); + DescriptorIndexNode childDescriptor = pstFile.getDescriptorIndexNode(emailRow.entryValueReference); + PSTObject child = PSTObject.detectAndLoadPSTObject(pstFile, childDescriptor); + output.add(child); + currentEmailIndex++; + } + } else if (fallbackEmailsTable != null) { + // we use the fallback + ListIterator iterator = this.fallbackEmailsTable.listIterator(currentEmailIndex); + for (int x = 0; x < numberToReturn; x++) { + if (this.currentEmailIndex >= this.getContentCount()) + { + // no more! + break; + } + DescriptorIndexNode childDescriptor = iterator.next(); + PSTObject child = PSTObject.detectAndLoadPSTObject(pstFile, childDescriptor); + output.add(child); + currentEmailIndex++; + } + } + + + return output; + } + + public LinkedList getChildDescriptorNodes() throws PSTException, IOException { + initEmailsTable(); + if (this.emailsTable == null) { + return new LinkedList(); + } + LinkedList output = new LinkedList(); + List> rows = this.emailsTable.getItems(); + for (HashMap row : rows) { + // get the emails from the rows + if (this.currentEmailIndex == this.getContentCount()) + { + // no more! + break; + } + PSTTable7CItem emailRow = row.get(0x67F2); + if (emailRow.entryValueReference == 0) { + break; + } + output.add(emailRow.entryValueReference); + } + return output; + } + + + + /** + * Get the next child of this folder + * As there could be thousands of emails, we have these kind of cursor operations + * @return the next email in the folder or null if at the end of the folder + * @throws PSTException + * @throws IOException + */ + public PSTObject getNextChild() + throws PSTException, IOException + { + initEmailsTable(); + + if (this.emailsTable != null) { + List> rows = this.emailsTable.getItems(currentEmailIndex, 1); + + if (this.currentEmailIndex == this.getContentCount()) + { + // no more! + return null; + } + // get the emails from the rows + PSTTable7CItem emailRow = rows.get(0).get(0x67F2); + DescriptorIndexNode childDescriptor = pstFile.getDescriptorIndexNode(emailRow.entryValueReference); + PSTObject child = PSTObject.detectAndLoadPSTObject(pstFile, childDescriptor); + currentEmailIndex++; + + return child; + } else if (this.fallbackEmailsTable != null) { + if (this.currentEmailIndex >= this.getContentCount() || this.currentEmailIndex >= this.fallbackEmailsTable.size()) + { + // no more! + return null; + } + // get the emails from the rows + DescriptorIndexNode childDescriptor = fallbackEmailsTable.get(currentEmailIndex); + PSTObject child = PSTObject.detectAndLoadPSTObject(pstFile, childDescriptor); + currentEmailIndex++; + return child; + } + return null; + } + + /** + * move the internal folder cursor to the desired position + * position 0 is before the first record. + * @param newIndex + */ + public void moveChildCursorTo(int newIndex) + throws IOException, PSTException + { + initEmailsTable(); + + if (newIndex < 1) { + currentEmailIndex = 0; + return; + } + if (newIndex > this.getContentCount()) { + newIndex = this.getContentCount(); + } + currentEmailIndex = newIndex; + } + + /** + * the number of child folders in this folder + * @return number of subfolders as counted + * @throws IOException + * @throws PSTException + */ + public int getSubFolderCount() + throws IOException, PSTException + { + this.initSubfoldersTable(); + if (this.subfoldersTable != null) + return this.subfoldersTable.getRowCount(); + else + return 0; + } + + /** + * the number of emails in this folder + * this is the count of emails made by the library and will therefore should be more accurate than getContentCount + * @return number of emails in this folder (as counted) + * @throws IOException + * @throws PSTException + public int getEmailCount() + throws IOException, PSTException + { + this.initEmailsTable(); + return this.emailsTable.getRowCount(); + } + */ + + + public int getFolderType() { + return this.getIntItem(0x3601); + } + + /** + * the number of emails in this folder + * this is as reported by the PST file, for a number calculated by the library use getEmailCount + * @return number of items as reported by PST File + */ + public int getContentCount() { + return this.getIntItem(0x3602); + } + + /** + * Amount of unread content items Integer 32-bit signed + */ + public int getUnreadCount() { + return this.getIntItem(0x3603); + } + + /** + * does this folder have subfolders + * once again, read from the PST, use getSubFolderCount if you want to know what the library makes of it all + * @return has subfolders as reported by the PST File + */ + public boolean hasSubfolders() { + return (this.getIntItem(0x360a) != 0); + } + + public String getContainerClass() { + return this.getStringItem(0x3613); + } + + public int getAssociateContentCount() { + return this.getIntItem(0x3617); + } + + /** + * Container flags Integer 32-bit signed + */ + public int getContainerFlags() { + return this.getIntItem(0x3600); + } + +} diff --git a/com/pff/objects/PSTMessage.java b/com/pff/objects/PSTMessage.java new file mode 100644 index 0000000..0ad6f9b --- /dev/null +++ b/com/pff/objects/PSTMessage.java @@ -0,0 +1,1029 @@ +/** + * 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.objects; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; + +import com.pff.PSTUtils; +import com.pff.exceptions.PSTException; +import com.pff.objects.sub.PSTRecipient; +import com.pff.parsing.DescriptorIndexNode; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.PSTNodeInputStream; +import com.pff.parsing.tables.PSTTable7C; +import com.pff.parsing.tables.PSTTable7CItem; +import com.pff.parsing.tables.PSTTableBC; +import com.pff.parsing.tables.PSTTableBCItem; +import com.pff.source.PSTSource; + +/** + * PST Message contains functions that are common across most MAPI objects. + * Note that many of these functions may not be applicable for the item in question, + * however there seems to be no hard and fast outline for what properties apply to which + * objects. For properties where no value is set, a blank value is returned (rather than + * an exception being raised). + * @author Richard Johnson + */ +public class PSTMessage extends PSTObject { + + public static final int IMPORTANCE_LOW = 0; + public static final int IMPORTANCE_NORMAL = 1; + public static final int IMPORTANCE_HIGH = 2; + + PSTMessage(PSTSource theFile, DescriptorIndexNode descriptorIndexNode) + throws PSTException, IOException + { + super(theFile, descriptorIndexNode); + } + + PSTMessage(PSTSource theFile, DescriptorIndexNode folderIndexNode, PSTTableBC table, HashMap localDescriptorItems) + { + super(theFile, folderIndexNode, table, localDescriptorItems); + } + + public String getRTFBody() + throws PSTException, IOException + { + // do we have an entry for it? + if (this.items.containsKey(0x1009)) + { + // is it a reference? + PSTTableBCItem item = this.items.get(0x1009); + if (item.data.length > 0) { + return (PSTUtils.decodeLZFU(item.data)); + } + int ref = item.entryValueReference; + PSTDescriptorItem descItem = this.localDescriptorItems.get(ref); + if ( descItem != null ) { + return PSTUtils.decodeLZFU(descItem.getData()); + } + } + + return ""; + } + + + + /** + * get the importance of the email + * @return IMPORTANCE_NORMAL if unknown + */ + public int getImportance() { + return getIntItem(0x0017, IMPORTANCE_NORMAL); + } + + /** + * get the message class for the email + * @return empty string if unknown + */ + public String getMessageClass() { + return this.getStringItem(0x001a); + } + + /** + * get the subject + * @return empty string if not found + */ + public String getSubject() { + String subject = this.getStringItem(0x0037); + +// byte[] controlCodesA = {0x01, 0x01}; +// byte[] controlCodesB = {0x01, 0x05}; +// byte[] controlCodesC = {0x01, 0x10}; + if ( subject != null && + (subject.length() >= 2) && + +// (subject.startsWith(new String(controlCodesA)) || +// subject.startsWith(new String(controlCodesB)) || +// subject.startsWith(new String(controlCodesC))) + subject.charAt(0) == 0x01 ) + { + if ( subject.length() == 2 ) { + subject = ""; + } else { + subject = subject.substring(2, subject.length()); + } + } + return subject; + } + + /** + * get the client submit time + * @return null if not found + */ + public Date getClientSubmitTime() { + return this.getDateItem(0x0039); + } + + /** + * get received by name + * @return empty string if not found + */ + public String getReceivedByName() { + return this.getStringItem(0x0040); + } + + /** + * get sent representing name + * @return empty string if not found + */ + public String getSentRepresentingName() { + return this.getStringItem(0x0042); + } + + /** + * Sent representing address type + * Known values are SMTP, EX (Exchange) and UNKNOWN + * @return empty string if not found + */ + public String getSentRepresentingAddressType() { + return this.getStringItem(0x0064); + } + + /** + * Sent representing email address + * @return empty string if not found + */ + public String getSentRepresentingEmailAddress() { + return this.getStringItem(0x0065); + } + + /** + * Conversation topic + * This is basically the subject from which Fwd:, Re, etc. has been removed + * @return empty string if not found + */ + public String getConversationTopic() { + return this.getStringItem(0x0070); + } + + /** + * Received by address type + * Known values are SMTP, EX (Exchange) and UNKNOWN + * @return empty string if not found + */ + public String getReceivedByAddressType() { + return this.getStringItem(0x0075); + } + + /** + * Received by email address + * @return empty string if not found + */ + public String getReceivedByAddress() { + return this.getStringItem(0x0076); + } + + /** + * Transport message headers ASCII or Unicode string These contain the SMTP e-mail headers. + */ + public String getTransportMessageHeaders() { + return this.getStringItem(0x007d); + } + + + public boolean isRead() { + return ((this.getIntItem(0x0e07) & 0x01) != 0); + } + public boolean isUnmodified() { + return ((this.getIntItem(0x0e07) & 0x02) != 0); + } + public boolean isSubmitted() { + return ((this.getIntItem(0x0e07) & 0x04) != 0); + } + public boolean isUnsent() { + return ((this.getIntItem(0x0e07) & 0x08) != 0); + } + public boolean hasAttachments() { + return ((this.getIntItem(0x0e07) & 0x10) != 0); + } + public boolean isFromMe() { + return ((this.getIntItem(0x0e07) & 0x20) != 0); + } + public boolean isAssociated() { + return ((this.getIntItem(0x0e07) & 0x40) != 0); + } + public boolean isResent() { + return ((this.getIntItem(0x0e07) & 0x80) != 0); + } + + + /** + * Acknowledgment mode Integer 32-bit signed + */ + public int getAcknowledgementMode () { + return this.getIntItem(0x0001); + } + /** + * Originator delivery report requested set if the sender wants a delivery report from all recipients 0 = false 0 != true + */ + public boolean getOriginatorDeliveryReportRequested () { + return (this.getIntItem(0x0023) != 0); + } + // 0x0025 0x0102 PR_PARENT_KEY Parent key Binary data Contains a GUID + /** + * Priority Integer 32-bit signed -1 = NonUrgent 0 = Normal 1 = Urgent + */ + public int getPriority () { + return this.getIntItem(0x0026); + } + /** + * Read Receipt Requested Boolean 0 = false 0 != true + */ + public boolean getReadReceiptRequested () { + return (this.getIntItem(0x0029) != 0); + } + /** + * Recipient Reassignment Prohibited Boolean 0 = false 0 != true + */ + public boolean getRecipientReassignmentProhibited () { + return (this.getIntItem(0x002b) != 0); + } + /** + * Original sensitivity Integer 32-bit signed the sensitivity of the message before being replied to or forwarded 0 = None 1 = Personal 2 = Private 3 = Company Confidential + */ + public int getOriginalSensitivity () { + return this.getIntItem(0x002e); + } + /** + * Sensitivity Integer 32-bit signed sender's opinion of the sensitivity of an email 0 = None 1 = Personal 2 = Private 3 = Company Confidential + */ + public int getSensitivity () { + return this.getIntItem(0x0036); + } + //0x003f 0x0102 PR_RECEIVED_BY_ENTRYID (PidTagReceivedByEntr yId) Received by entry identifier Binary data Contains recipient/sender structure + //0x0041 0x0102 PR_SENT_REPRESENTING_ENTRYID Sent representing entry identifier Binary data Contains recipient/sender structure + //0x0043 0x0102 PR_RCVD_REPRESENTING_ENTRYID Received representing entry identifier Binary data Contains recipient/sender structure + + /* + * Address book search key + */ + public byte[] getPidTagSentRepresentingSearchKey() + { + return this.getBinaryItem(0x003b); + } + /** + * Received representing name ASCII or Unicode string + */ + public String getRcvdRepresentingName () { + return this.getStringItem(0x0044); + } + /** + * Original subject ASCII or Unicode string + */ + public String getOriginalSubject () { + return this.getStringItem(0x0049); + } +// 0x004e 0x0040 PR_ORIGINAL_SUBMIT_TIME Original submit time Filetime + /** + * Reply recipients names ASCII or Unicode string + */ + public String getReplyRecipientNames () { + return this.getStringItem(0x0050); + } + /** + * My address in To field Boolean + */ + public boolean getMessageToMe () { + return (this.getIntItem(0x0057) != 0); + } + /** + * My address in CC field Boolean + */ + public boolean getMessageCcMe () { + return (this.getIntItem(0x0058) != 0); + } + /** + * Message addressed to me ASCII or Unicode string + */ + public String getMessageRecipMe () { + return this.getStringItem(0x0059); + } + /** + * Response requested Boolean + */ + public boolean getResponseRequested () { + return getBooleanItem(0x0063); + } + /** + * Sent representing address type ASCII or Unicode string Known values are SMTP, EX (Exchange) and UNKNOWN + */ + public String getSentRepresentingAddrtype () { + return this.getStringItem(0x0064); + } + //0x0071 0x0102 PR_CONVERSATION_INDEX (PidTagConversationInd ex) Conversation index Binary data + /** + * Original display BCC ASCII or Unicode string + */ + public String getOriginalDisplayBcc () { + return this.getStringItem(0x0072); + } + /** + * Original display CC ASCII or Unicode string + */ + public String getOriginalDisplayCc () { + return this.getStringItem(0x0073); + } + /** + * Original display TO ASCII or Unicode string + */ + public String getOriginalDisplayTo () { + return this.getStringItem(0x0074); + } + /** + * Received representing address type. + * Known values are SMTP, EX (Exchange) and UNKNOWN + */ + public String getRcvdRepresentingAddrtype () { + return this.getStringItem(0x0077); + } + /** + * Received representing e-mail address + */ + public String getRcvdRepresentingEmailAddress() { + return this.getStringItem(0x0078); + } + + /** + * Recipient details + */ + + /** + * Non receipt notification requested + */ + public boolean isNonReceiptNotificationRequested() { + return (this.getIntItem(0x0c06) != 0); + } + + /** + * Originator non delivery report requested + */ + public boolean isOriginatorNonDeliveryReportRequested() { + return (this.getIntItem(0x0c08) != 0); + } + + public static final int RECIPIENT_TYPE_TO = 1; + public static final int RECIPIENT_TYPE_CC = 2; + + /** + * Recipient type Integer 32-bit signed 0x01 => To 0x02 =>CC + */ + public int getRecipientType() { + return this.getIntItem(0x0c15); + } + + /** + * Reply requested + */ + public boolean isReplyRequested() { + return (this.getIntItem(0x0c17) != 0); + } + + /* + * Sending mailbox owner's address book entry ID + */ + public byte[] getSenderEntryId() { + return this.getBinaryItem(0x0c19); + } + + /** + * Sender name + */ + public String getSenderName() { + return this.getStringItem(0x0c1a); + } + + /** + * Sender address type. + * Known values are SMTP, EX (Exchange) and UNKNOWN + */ + public String getSenderAddrtype() { + return this.getStringItem(0x0c1e); + } + + /** + * Sender e-mail address + */ + public String getSenderEmailAddress() { + return this.getStringItem(0x0c1f); + } + + /** + * Non-transmittable message properties + */ + + /** + * Message size + */ + public long getMessageSize() { + return this.getLongItem(0x0e08); + } + /** + * Internet article number + */ + public int getInternetArticleNumber() { + return this.getIntItem(0x0e23); + } + + /* + * Server that the client should attempt to send the mail with + */ + public String getPrimarySendAccount() { + return this.getStringItem(0x0e28); + } + + /* + * Server that the client is currently using to send mail + */ + public String getNextSendAcct() { + return this.getStringItem(0x0e29); + } + + /** + * URL computer name postfix + */ + public int getURLCompNamePostfix() { + return this.getIntItem(0x0e61); + } + /** + * Object type + */ + public int getObjectType() { + return this.getIntItem(0x0ffe); + } + /** + * Delete after submit + */ + public boolean getDeleteAfterSubmit() { + return ((this.getIntItem(0x0e01)) != 0); + } + /** + * Responsibility + */ + public boolean getResponsibility() { + return ((this.getIntItem(0x0e0f)) != 0); + } + /** + * Compressed RTF in Sync Boolean + */ + public boolean isRTFInSync() { + return ((this.getIntItem(0x0e1f)) != 0); + } + /** + * URL computer name set + */ + public boolean isURLCompNameSet() { + return ((this.getIntItem(0x0e62)) != 0); + } + /** + * Display BCC + */ + public String getDisplayBCC() { + return this.getStringItem(0x0e02); + } + /** + * Display CC + */ + public String getDisplayCC() { + return this.getStringItem(0x0e03); + } + /** + * Display To + */ + public String getDisplayTo() { + return this.getStringItem(0x0e04); + } + /** + * Message delivery time + */ + public Date getMessageDeliveryTime() { + return this.getDateItem(0x0e06); + } + +// +// public int getFlags() { +// if (this.items.containsKey(0x0e17)) { +// System.out.println(this.items.get(0x0e17)); +// } +// return this.getIntItem(0x0e17); +// } +// +// /** +// * The message is to be highlighted in recipients' folder displays. +// */ +// public boolean isHighlighted() { +// return (this.getIntItem(0x0e17) & 0x1) != 0; +// } +// +// /** +// * The message has been tagged for a client-defined purpose. +// */ +// public boolean isTagged() { +// return (this.getIntItem(0x0e17) & 0x2) != 0; +// } +// +// /** +// * The message is to be suppressed from recipients' folder displays. +// */ +// public boolean isHidden() { +// return (this.getIntItem(0x0e17) & 0x4) != 0; +// } +// +// /** +// * The message has been marked for subsequent deletion +// */ +// public boolean isDelMarked() { +// return (this.getIntItem(0x0e17) & 0x8) != 0; +// } +// +// /** +// * The message is in draft revision status. +// */ +// public boolean isDraft() { +// return (this.getIntItem(0x0e17) & 0x100) != 0; +// } +// +// /** +// * The message has been replied to. +// */ +// public boolean isAnswered() { +// return (this.getIntItem(0x0e17) & 0x200) != 0; +// } +// +// /** +// * The message has been marked for downloading from the remote message store to the local client +// */ +// public boolean isMarkedForDownload() { +// return (this.getIntItem(0x0e17) & 0x1000) != 0; +// } +// +// /** +// * The message has been marked for deletion at the remote message store without downloading to the local client. +// */ +// public boolean isRemoteDelMarked() { +// return (this.getIntItem(0x0e17) & 0x2000) != 0; +// } + + /** + * Message content properties + */ + + /** + * Plain text e-mail body + */ + public String getBody() { + String cp = null; + PSTTableBCItem cpItem = this.items.get(0x3FFD); // PidTagMessageCodepage + if (cpItem == null) { + cpItem = this.items.get(0x3FDE); // PidTagInternetCodepage + } + if (cpItem != null) { + cp = PSTSource.getInternetCodePageCharset(cpItem.entryValueReference); + } + return this.getStringItem(0x1000, 0, cp); + } + /* + * Plain text body prefix + */ + public String getBodyPrefix() { + return this.getStringItem(0x6619); + } + /** + * RTF Sync Body CRC + */ + public int getRTFSyncBodyCRC() { + return this.getIntItem(0x1006); + } + /** + * RTF Sync Body character count + */ + public int getRTFSyncBodyCount() { + return this.getIntItem(0x1007); + } + /** + * RTF Sync body tag + */ + public String getRTFSyncBodyTag() { + return this.getStringItem(0x1008); + } + /** + * RTF whitespace prefix count + */ + public int getRTFSyncPrefixCount() { + return this.getIntItem(0x1010); + } + /** + * RTF whitespace tailing count + */ + public int getRTFSyncTrailingCount() { + return this.getIntItem(0x1011); + } + /** + * HTML e-mail body + */ + public String getBodyHTML() { + String cp = null; + PSTTableBCItem cpItem = this.items.get(0x3FDE); // PidTagInternetCodepage + if (cpItem == null) { + cpItem = this.items.get(0x3FFD); // PidTagMessageCodepage + } + if (cpItem != null) { + cp = PSTSource.getInternetCodePageCharset(cpItem.entryValueReference); + } + return this.getStringItem(0x1013, 0, cp); + } + + /** + * Message ID for this email as allocated per rfc2822 + */ + public String getInternetMessageId() { + return this.getStringItem(0x1035); + } + /** + * In-Reply-To + */ + public String getInReplyToId() { + return this.getStringItem(0x1042); + } + /** + * Return Path + */ + public String getReturnPath() { + return this.getStringItem(0x1046); + } + /** + * Icon index + */ + public int getIconIndex() { + return this.getIntItem(0x1080); + } + + /** + * Action flag + * This relates to the replying / forwarding of messages. + * It is classified as "unknown" atm, so just provided here + * in case someone works out what all the various flags mean. + */ + public int getActionFlag() { + return this.getIntItem(0x1081); + } + /** + * is the action flag for this item "forward"? + */ + public boolean hasForwarded() { + int actionFlag = this.getIntItem(0x1081); + return ((actionFlag & 0x8) > 0); + } + /** + * is the action flag for this item "replied"? + */ + public boolean hasReplied() { + int actionFlag = this.getIntItem(0x1081); + return ((actionFlag & 0x4) > 0); + } + /** + * the date that this item had an action performed (eg. replied or forwarded) + */ + public Date getActionDate() { + return this.getDateItem(0x1082); + } + + /** + * Disable full fidelity + */ + public boolean getDisableFullFidelity() { + return (this.getIntItem(0x10f2) != 0); + } + /** + * URL computer name + * Contains the .eml file name + */ + public String getURLCompName() { + return this.getStringItem(0x10f3); + } + /** + * Attribute hidden + */ + public boolean getAttrHidden() { + return (this.getIntItem(0x10f4) != 0); + } + /** + * Attribute system + */ + public boolean getAttrSystem() { + return (this.getIntItem(0x10f5) != 0); + } + /** + * Attribute read only + */ + public boolean getAttrReadonly() { + return (this.getIntItem(0x10f6) != 0); + } + + + private PSTTable7C recipientTable = null; + /** + * find, extract and load up all of the attachments in this email + * necessary for the other operations. + * @throws PSTException + * @throws IOException + */ + private void processRecipients() + { + try { + int recipientTableKey = 0x0692; + if (this.recipientTable == null && + this.localDescriptorItems != null && + this.localDescriptorItems.containsKey(recipientTableKey)) + { + PSTDescriptorItem item = this.localDescriptorItems.get(recipientTableKey); + HashMap descriptorItems = null; + if (item.getSubNodeOffsetIndexIdentifier() > 0) { + descriptorItems =pstFile.getPSTDescriptorItems(item.getSubNodeOffsetIndexIdentifier()); + } + recipientTable = new PSTTable7C(new PSTNodeInputStream(pstFile, item), descriptorItems); + } + } catch ( Exception e ) { + e.printStackTrace(); + recipientTable = null; + } + } + + /** + * get the number of recipients for this message + * @throws PSTException + * @throws IOException + */ + public int getNumberOfRecipients() + throws PSTException, IOException + { + this.processRecipients(); + + // still nothing? must be no recipients... + if ( this.recipientTable == null ) { + return 0; + } + return this.recipientTable.getRowCount(); + } + + /** + * attachment stuff here, not sure if these can just exist in emails or not, + * but a table key of 0x0671 would suggest that this is a property of the envelope + * rather than a specific email property + */ + + private PSTTable7C attachmentTable = null; + + /** + * find, extract and load up all of the attachments in this email + * necessary for the other operations. + * @throws PSTException + * @throws IOException + */ + private void processAttachments() + throws PSTException, IOException + { + int attachmentTableKey = 0x0671; + if (this.attachmentTable == null && + this.localDescriptorItems != null && + this.localDescriptorItems.containsKey(attachmentTableKey)) + { + PSTDescriptorItem item = this.localDescriptorItems.get(attachmentTableKey); + HashMap descriptorItems = null; + if (item.getSubNodeOffsetIndexIdentifier() > 0) { + descriptorItems =pstFile.getPSTDescriptorItems(item.getSubNodeOffsetIndexIdentifier()); + } + attachmentTable = new PSTTable7C(new PSTNodeInputStream(pstFile, item), descriptorItems); + } + } + + /** + * Start date Filetime + */ + public Date getTaskStartDate() { + return getDateItem(pstFile.getNameToIdMapItem(0x00008104, PSTSource.PSETID_Task)); + } + /** + * Due date Filetime + */ + public Date getTaskDueDate() { + return getDateItem(pstFile.getNameToIdMapItem(0x00008105, PSTSource.PSETID_Task)); + } + + /** + * Is a reminder set on this object? + * @return + */ + public boolean getReminderSet() { + return getBooleanItem(pstFile.getNameToIdMapItem(0x00008503, PSTSource.PSETID_Common)); + } + + public int getReminderDelta() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008501, PSTSource.PSETID_Common)); + } + + /** + * "flagged" items are actually emails with a due date. + * This convience method just checks to see if that is true. + */ + public boolean isFlagged() { + return getTaskDueDate() != null; + } + + /** + * get the categories defined for this message + */ + public String[] getColorCategories() + throws PSTException + { + int keywordCategory = pstFile.getPublicStringToIdMapItem("Keywords"); + + String[] categories = new String[0]; + if (this.items.containsKey(keywordCategory)) { + try { + PSTTableBCItem item = this.items.get(keywordCategory); + if (item.data.length == 0) { + return categories; + } + int categoryCount = item.data[0]; + if (categoryCount > 0) { + categories = new String[categoryCount]; + int[] offsets = new int[categoryCount]; + for (int x = 0; x < categoryCount; x++) { + offsets[x] = (int)PSTUtils.convertBigEndianBytesToLong(item.data, (x*4)+1, (x+1)*4+1); + } + for (int x = 0; x < offsets.length -1; x++) { + int start = offsets[x]; + int end = offsets[x+1]; + int length = (end-start); + byte[] string = new byte[length]; + System.arraycopy(item.data, start, string, 0, length); + String name = new String(string, "UTF-16LE"); + categories[x] = name; + } + int start = offsets[offsets.length-1]; + int end = item.data.length; + int length = (end-start); + byte[] string = new byte[length]; + System.arraycopy(item.data, start, string, 0, length); + String name = new String(string, "UTF-16LE"); + categories[categories.length-1] = name; + } + } catch (Exception err) { + throw new PSTException("Unable to decode category data", err); + } + } + return categories; + } + + /** + * get the number of attachments for this message + * @throws PSTException + * @throws IOException + */ + public int getNumberOfAttachments() + { + try { + this.processAttachments(); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + + + // still nothing? must be no attachments... + if ( this.attachmentTable == null ) { + return 0; + } + return this.attachmentTable.getRowCount(); + } + + /** + * get a specific attachment from this email. + * @param attachmentNumber + * @return the attachment at the defined index + * @throws PSTException + * @throws IOException + */ + public PSTAttachment getAttachment(int attachmentNumber) + throws PSTException, IOException + { + this.processAttachments(); + + int attachmentCount = 0; + if ( this.attachmentTable != null ) { + attachmentCount = this.attachmentTable.getRowCount(); + } + + if (attachmentNumber >= attachmentCount) { + throw new PSTException("unable to fetch attachment number "+attachmentNumber+", only "+attachmentCount+" in this email"); + } + + // we process the C7 table here, basically we just want the attachment local descriptor... + HashMap attachmentDetails = this.attachmentTable.getItems().get(attachmentNumber); + PSTTable7CItem attachmentTableItem = attachmentDetails.get(0x67f2); + int descriptorItemId = attachmentTableItem.entryValueReference; + + // get the local descriptor for the attachmentDetails table. + PSTDescriptorItem descriptorItem = this.localDescriptorItems.get(descriptorItemId); + + // try and decode it + byte[] attachmentData = descriptorItem.getData(); + if ( attachmentData != null && attachmentData.length > 0 ) { + //PSTTableBC attachmentDetailsTable = new PSTTableBC(descriptorItem.getData(), descriptorItem.getBlockOffsets()); + PSTTableBC attachmentDetailsTable = new PSTTableBC(new PSTNodeInputStream(pstFile, descriptorItem)); + + // create our all-precious attachment object. + // note that all the information that was in the c7 table is repeated in the eb table in attachment data. + // so no need to pass it... + HashMap attachmentDescriptorItems = new HashMap(); + if (descriptorItem.getSubNodeOffsetIndexIdentifier() > 0) { + attachmentDescriptorItems = pstFile.getPSTDescriptorItems(descriptorItem.getSubNodeOffsetIndexIdentifier()); + } + return new PSTAttachment(this.pstFile, attachmentDetailsTable, attachmentDescriptorItems); + } + + throw new PSTException("unable to fetch attachment number "+attachmentNumber+", unable to read attachment details table"); + } + + /** + * get a specific recipient from this email. + * @param recipientNumber + * @return the recipient at the defined index + * @throws PSTException + * @throws IOException + */ + public PSTRecipient getRecipient(int recipientNumber) + throws PSTException, IOException + { + if ( recipientNumber >= getNumberOfRecipients() || recipientNumber >= recipientTable.getItems().size() ) + { + throw new PSTException("unable to fetch recipient number "+recipientNumber); + } + + HashMap recipientDetails = recipientTable.getItems().get(recipientNumber); + + if ( recipientDetails != null ) { + return new PSTRecipient(recipientDetails); + } + + return null; + } + + + public String getRecipientsString() { + if ( recipientTable != null ) { + return recipientTable.getItemsString(); + } + + return "No recipients table!"; + } + + + /** + * string representation of this email + */ + public String toString() { + return + "PSTEmail: "+this.getSubject()+"\n"+ + "Importance: "+this.getImportance()+"\n"+ + "Message Class: "+this.getMessageClass() + "\n\n" + + this.getTransportMessageHeaders()+"\n\n\n"+ + this.items+ + this.localDescriptorItems; + } + +} diff --git a/com/pff/objects/PSTMessageStore.java b/com/pff/objects/PSTMessageStore.java new file mode 100644 index 0000000..b231aab --- /dev/null +++ b/com/pff/objects/PSTMessageStore.java @@ -0,0 +1,97 @@ +/** + * 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.objects; + +import java.io.*; +import java.util.*; + +import com.pff.PSTUtils; +import com.pff.exceptions.PSTException; +import com.pff.parsing.DescriptorIndexNode; +import com.pff.parsing.tables.PSTTableBCItem; +import com.pff.source.PSTSource; + + +/** + * Object that represents the message store. + * Not much use other than to get the "name" of the PST file. + * @author Richard Johnson + */ +public class PSTMessageStore extends PSTObject { + + public PSTMessageStore(PSTSource theFile, DescriptorIndexNode descriptorIndexNode) + throws PSTException, IOException + { + super(theFile, descriptorIndexNode); + } + + /** + * Get the tag record key, unique to this pst + */ + public UUID getTagRecordKeyAsUUID() { + // attempt to find in the table. + int guidEntryType = 0x0ff9; + if (this.items.containsKey(guidEntryType)) { + PSTTableBCItem item = this.items.get(guidEntryType); + int offset = 0; + byte[] bytes = item.data; + long mostSigBits = (PSTUtils.convertLittleEndianBytesToLong(bytes, offset, offset+4) << 32) | + (PSTUtils.convertLittleEndianBytesToLong(bytes, offset+4, offset+6) << 16) | + PSTUtils.convertLittleEndianBytesToLong(bytes, offset+6, offset+8); + long leastSigBits = PSTUtils.convertBigEndianBytesToLong(bytes, offset+8, offset+16); + return new UUID(mostSigBits, leastSigBits); + } + return null; + } + + /** + * get the message store display name + */ + public String getDisplayName() { + // attempt to find in the table. + int displayNameEntryType = 0x3001; + if (this.items.containsKey(displayNameEntryType)) { + return this.getStringItem(displayNameEntryType); + //PSTTableBCItem item = (PSTTableBCItem)this.items.get(displayNameEntryType); + //return new String(item.getStringValue()); + } + return ""; + } + + + public String getDetails() { + return this.items.toString(); + } + +} diff --git a/com/pff/objects/PSTObject.java b/com/pff/objects/PSTObject.java new file mode 100644 index 0000000..4371e62 --- /dev/null +++ b/com/pff/objects/PSTObject.java @@ -0,0 +1,549 @@ +/** + * 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.objects; + +import java.io.IOException; +import java.util.*; + +import com.pff.PSTUtils; +import com.pff.exceptions.PSTException; +import com.pff.objects.sub.PSTTimeZone; +import com.pff.parsing.DescriptorIndexNode; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.PSTNodeInputStream; +import com.pff.parsing.tables.PSTTableBC; +import com.pff.parsing.tables.PSTTableBCItem; +import com.pff.source.PSTSource; + +/** + * PST Object is the root class of all PST Items. + * It also provides a number of static utility functions. The most important of which is the + * detectAndLoadPSTObject call which allows extraction of a PST Item from the file. + * @author Richard Johnson + */ +public class PSTObject { + + public static final int NID_TYPE_HID = 0x00; // Heap node + public static final int NID_TYPE_INTERNAL = 0x01; // Internal node (section 2.4.1) + public static final int NID_TYPE_NORMAL_FOLDER = 0x02; // Normal Folder object (PC) + public static final int NID_TYPE_SEARCH_FOLDER = 0x03; // Search Folder object (PC) + public static final int NID_TYPE_NORMAL_MESSAGE = 0x04; // Normal Message object (PC) + public static final int NID_TYPE_ATTACHMENT = 0x05; // Attachment object (PC) + public static final int NID_TYPE_SEARCH_UPDATE_QUEUE = 0x06; // Queue of changed objects for search Folder objects + public static final int NID_TYPE_SEARCH_CRITERIA_OBJECT = 0x07; // Defines the search criteria for a search Folder object + public static final int NID_TYPE_ASSOC_MESSAGE = 0x08; // Folder associated information (FAI) Message object (PC) + public static final int NID_TYPE_CONTENTS_TABLE_INDEX = 0x0A; // Internal, persisted view-related + public static final int NID_TYPE_RECEIVE_FOLDER_TABLE = 0X0B; // Receive Folder object (Inbox) + public static final int NID_TYPE_OUTGOING_QUEUE_TABLE = 0x0C; // Outbound queue (Outbox) + public static final int NID_TYPE_HIERARCHY_TABLE = 0x0D; // Hierarchy table (TC) + public static final int NID_TYPE_CONTENTS_TABLE = 0x0E; // Contents table (TC) + public static final int NID_TYPE_ASSOC_CONTENTS_TABLE = 0x0F; // FAI contents table (TC) + public static final int NID_TYPE_SEARCH_CONTENTS_TABLE = 0x10; // Contents table (TC) of a search Folder object + public static final int NID_TYPE_ATTACHMENT_TABLE = 0x11; // Attachment table (TC) + public static final int NID_TYPE_RECIPIENT_TABLE = 0x12; // Recipient table (TC) + public static final int NID_TYPE_SEARCH_TABLE_INDEX = 0x13; // Internal, persisted view-related + public static final int NID_TYPE_LTP = 0x1F; // LTP + + + + public String getItemsString() { + return items.toString(); + } + + protected PSTSource pstFile; + protected byte[] data; + protected DescriptorIndexNode descriptorIndexNode; + protected HashMap items; + protected HashMap localDescriptorItems = null; + + protected LinkedHashMap> children; + + protected PSTObject(PSTSource theFile, DescriptorIndexNode descriptorIndexNode) + throws PSTException, IOException + { + this.pstFile = theFile; + this.descriptorIndexNode = descriptorIndexNode; + + //descriptorIndexNode.readData(theFile); + //PSTTableBC table = new PSTTableBC(descriptorIndexNode.dataBlock.data, descriptorIndexNode.dataBlock.blockOffsets); + PSTTableBC table = new PSTTableBC(new PSTNodeInputStream(pstFile, pstFile.getOffsetIndexNode(descriptorIndexNode.dataOffsetIndexIdentifier))); + //System.out.println(table); + this.items = table.getItems(); + + if (descriptorIndexNode.localDescriptorsOffsetIndexIdentifier != 0) { + //PSTDescriptor descriptor = new PSTDescriptor(theFile, descriptorIndexNode.localDescriptorsOffsetIndexIdentifier); + //localDescriptorItems = descriptor.getChildren(); + this.localDescriptorItems = theFile.getPSTDescriptorItems(descriptorIndexNode.localDescriptorsOffsetIndexIdentifier); + } + } + + /** + * for pre-population + * @param theFile + * @param folderIndexNode + * @param table + */ + protected PSTObject(PSTSource theFile, DescriptorIndexNode folderIndexNode, PSTTableBC table, HashMap localDescriptorItems) { + this.pstFile = theFile; + this.descriptorIndexNode = folderIndexNode; + this.items = table.getItems(); + this.table = table; + this.localDescriptorItems = localDescriptorItems; + } + protected PSTTableBC table; + + + + /** + * get the descriptor node for this item + * this identifies the location of the node in the BTree and associated info + * @return item's descriptor node + */ + public DescriptorIndexNode getDescriptorNode() { + return this.descriptorIndexNode; + } + /** + * get the descriptor identifier for this item + * can be used for loading objects through detectAndLoadPSTObject(PSTFile theFile, long descriptorIndex) + * @return item's descriptor node identifier + */ + public long getDescriptorNodeId() { + return this.descriptorIndexNode.descriptorIdentifier; + } + + public int getNodeType() { + return PSTObject.getNodeType(this.descriptorIndexNode.descriptorIdentifier); + } + public static int getNodeType(int descriptorIdentifier) { + return descriptorIdentifier & 0x1F; + } + + + protected int getIntItem(int identifier) { + return getIntItem(identifier, 0); + } + protected int getIntItem(int identifier, int defaultValue) { + if (this.items.containsKey(identifier)) { + PSTTableBCItem item = this.items.get(identifier); + return item.entryValueReference; + } + return defaultValue; + } + + protected boolean getBooleanItem(int identifier) { + return getBooleanItem(identifier, false); + } + protected boolean getBooleanItem(int identifier, boolean defaultValue) { + if (this.items.containsKey(identifier)) { + PSTTableBCItem item = this.items.get(identifier); + return item.entryValueReference != 0; + } + return defaultValue; + } + + protected double getDoubleItem(int identifier) { + return getDoubleItem(identifier, 0); + } + protected double getDoubleItem(int identifier, double defaultValue) { + if (this.items.containsKey(identifier)) { + PSTTableBCItem item = this.items.get(identifier); + long longVersion = PSTUtils.convertLittleEndianBytesToLong(item.data); + return Double.longBitsToDouble(longVersion); + } + return defaultValue; + } + + + protected long getLongItem(int identifier) { + return getLongItem(identifier, 0); + } + protected long getLongItem(int identifier, long defaultValue) { + if (this.items.containsKey(identifier)) { + PSTTableBCItem item = this.items.get(identifier); + if (item.entryValueType == 0x0003) { + // we are really just an int + return item.entryValueReference; + } else if ( item.entryValueType == 0x0014 ){ + // we are a long + if ( item.data != null && item.data.length == 8 ) { + return PSTUtils.convertLittleEndianBytesToLong(item.data, 0, 8); + } else { + System.err.printf("Invalid data length for long id 0x%04X\n", identifier); + // Return the default value for now... + } + } + } + return defaultValue; + } + + protected String getStringItem(int identifier) { + return getStringItem(identifier, 0); + } + protected String getStringItem(int identifier, int stringType) { + return getStringItem(identifier, stringType, null); + } + protected String getStringItem(int identifier, int stringType, String codepage) { + PSTTableBCItem item = this.items.get(identifier); + if ( item != null ) { + + if (codepage == null) { + codepage = this.getStringCodepage(); + } + + // get the string type from the item if not explicitly set + if ( stringType == 0 ) { + stringType = item.entryValueType; + } + + // see if there is a descriptor entry + if ( !item.isExternalValueReference ) { + //System.out.println("here: "+new String(item.data)+this.descriptorIndexNode.descriptorIdentifier); + return PSTObject.createJavaString(item.data, stringType, codepage); + } + if (this.localDescriptorItems != null && + this.localDescriptorItems.containsKey(item.entryValueReference)) + { + // we have a hit! + PSTDescriptorItem descItem = this.localDescriptorItems.get(item.entryValueReference); + + try { + byte[] data = descItem.getData(); + if ( data == null ) { + return ""; + } + + return PSTObject.createJavaString(data, stringType, codepage); + } catch (Exception e) { + System.err.printf("Exception %s decoding string %s: %s\n", + e.toString(), + PSTSource.getPropertyDescription(identifier, stringType), data != null ? data.toString() : "null"); + return ""; + } + //System.out.printf("PSTObject.getStringItem - item isn't a string: 0x%08X\n", identifier); + //return ""; + } + + return PSTObject.createJavaString(data, stringType, codepage); + } + return ""; + } + + static String createJavaString(byte[] data, int stringType, String codepage) + { + try { + if ( stringType == 0x1F ) { + return new String(data, "UTF-16LE"); + } + + if (codepage == null) { + return new String(data); + } else { + codepage = codepage.toUpperCase(); + return new String(data, codepage); + } + /* + if (codepage == null || codepage.toUpperCase().equals("UTF-8") || codepage.toUpperCase().equals("UTF-7")) { + // PST UTF-8 strings are not... really UTF-8 + // it seems that they just don't use multibyte chars at all. + // indeed, with some crylic chars in there, the difficult chars are just converted to %3F(?) + // I suspect that outlook actually uses RTF to store these problematic strings. + StringBuffer sbOut = new StringBuffer(); + for (int x = 0; x < data.length; x++) { + sbOut.append((char)(data[x] & 0xFF)); // just blindly accept the byte as a UTF char, seems right half the time + } + return new String(sbOut); + } else { + codepage = codepage.toUpperCase(); + return new String(data, codepage); + } + */ + } catch (Exception err) { + System.err.println("Unable to decode string"); + err.printStackTrace(); + return ""; + } + } + + private String getStringCodepage() { + // try and get the codepage + PSTTableBCItem cpItem = this.items.get(0x3FFD); // PidTagMessageCodepage + if (cpItem == null) { + cpItem = this.items.get(0x3FDE); // PidTagInternetCodepage + } + if (cpItem != null) { + return PSTSource.getInternetCodePageCharset(cpItem.entryValueReference); + } + return null; + } + + public Date getDateItem(int identifier) { + if ( this.items.containsKey(identifier) ) { + PSTTableBCItem item = this.items.get(identifier); + if (item.data.length == 0 ) { + return new Date(0); + } + int high = (int)PSTUtils.convertLittleEndianBytesToLong(item.data, 4, 8); + int low = (int)PSTUtils.convertLittleEndianBytesToLong(item.data, 0, 4); + + return PSTUtils.filetimeToDate(high, low); + } + return null; + } + + protected byte[] getBinaryItem(int identifier) { + if (this.items.containsKey(identifier)) { + PSTTableBCItem item = this.items.get(identifier); + if ( item.entryValueType == 0x0102 ) { + if ( !item.isExternalValueReference ) { + return item.data; + } + if ( this.localDescriptorItems != null && + this.localDescriptorItems.containsKey(item.entryValueReference)) + { + // we have a hit! + PSTDescriptorItem descItem = this.localDescriptorItems.get(item.entryValueReference); + try { + return descItem.getData(); + } catch (Exception e) { + System.err.printf("Exception reading binary item: reference 0x%08X\n", item.entryValueReference); + + return null; + } + } + + //System.out.println("External reference!!!\n"); + } + } + return null; + } + + protected PSTTimeZone getTimeZoneItem(int identifier) { + byte[] tzData = getBinaryItem(identifier); + if ( tzData != null && tzData.length != 0 ) { + return new PSTTimeZone(tzData); + } + return null; + } + + public String getMessageClass() { + return this.getStringItem(0x001a); + } + + public String toString() { + return this.localDescriptorItems + "\n" + + (this.items); + } + + /** + * These are the common properties, some don't really appear to be common across folders and emails, but hey + */ + + /** + * get the display name + */ + public String getDisplayName() { + return this.getStringItem(0x3001); + } + /** + * Address type + * Known values are SMTP, EX (Exchange) and UNKNOWN + */ + public String getAddrType() { + return this.getStringItem(0x3002); + } + /** + * E-mail address + */ + public String getEmailAddress() { + return this.getStringItem(0x3003); + } + /** + * Comment + */ + public String getComment() { + return this.getStringItem(0x3004); + } + /** + * Creation time + */ + public Date getCreationTime() { + return this.getDateItem(0x3007); + } + /** + * Modification time + */ + public Date getLastModificationTime() { + return this.getDateItem(0x3008); + } + + + + /** + * Detect and load a PST Object from a file with the specified descriptor index + * @param theFile + * @param descriptorIndex + * @return PSTObject with that index + * @throws IOException + * @throws PSTException + */ + public static PSTObject detectAndLoadPSTObject(PSTSource theFile, long descriptorIndex) + throws IOException, PSTException + { + return detectAndLoadPSTObject(theFile, theFile.getDescriptorIndexNode(descriptorIndex)); + } + + /** + * Detect and load a PST Object from a file with the specified descriptor index + * @param theFile + * @param folderIndexNode + * @return PSTObject with that index + * @throws IOException + * @throws PSTException + */ + public static PSTObject detectAndLoadPSTObject(PSTSource theFile, DescriptorIndexNode folderIndexNode) + throws IOException, PSTException + { + int nidType = (folderIndexNode.descriptorIdentifier & 0x1F); + if ( nidType == 0x02 || nidType == 0x03 || nidType == 0x04 ) { + + PSTTableBC table = new PSTTableBC(new PSTNodeInputStream(theFile, theFile.getOffsetIndexNode(folderIndexNode.dataOffsetIndexIdentifier))); + + HashMap localDescriptorItems = null; + if (folderIndexNode.localDescriptorsOffsetIndexIdentifier != 0) { + localDescriptorItems = theFile.getPSTDescriptorItems(folderIndexNode.localDescriptorsOffsetIndexIdentifier); + } + + if ( nidType == 0x02 || nidType == 0x03 ) { + return new PSTFolder(theFile, folderIndexNode, table, localDescriptorItems); + } else { + return createAppropriatePSTMessageObject(theFile, folderIndexNode, table, localDescriptorItems); + } + } + else + { + throw new PSTException("Unknown child type with offset id: "+folderIndexNode.localDescriptorsOffsetIndexIdentifier); + } + } + + + + static PSTMessage createAppropriatePSTMessageObject(PSTSource theFile, DescriptorIndexNode folderIndexNode, PSTTableBC table, HashMap localDescriptorItems) + { + + PSTTableBCItem item = table.getItems().get(0x001a); + String messageClass = ""; + if ( item != null ) + { + messageClass = item.getStringValue(); + } + + if (messageClass.equals("IPM.Note")) { + return new PSTMessage(theFile, folderIndexNode, table, localDescriptorItems); + } else if (messageClass.equals("IPM.Appointment") || + messageClass.equals("IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}") || + messageClass.startsWith("IPM.Schedule.Meeting")) { + return new PSTAppointment(theFile, folderIndexNode, table, localDescriptorItems); + } else if (messageClass.equals("IPM.Contact")) { + return new PSTContact(theFile, folderIndexNode, table, localDescriptorItems); + } else if (messageClass.equals("IPM.Task")) { + return new PSTTask(theFile, folderIndexNode, table, localDescriptorItems); + } else if (messageClass.equals("IPM.Activity")) { + return new PSTActivity(theFile, folderIndexNode, table, localDescriptorItems); + } else if (messageClass.equals("IPM.Post.Rss")) { + return new PSTRss(theFile, folderIndexNode, table, localDescriptorItems); + } else { + System.err.println("Unknown message type: "+messageClass); + } + + return new PSTMessage(theFile, folderIndexNode, table, localDescriptorItems); + } + + + public static String guessPSTObjectType(PSTSource theFile, DescriptorIndexNode folderIndexNode) + throws IOException, PSTException + { + + PSTTableBC table = new PSTTableBC(new PSTNodeInputStream(theFile, theFile.getOffsetIndexNode(folderIndexNode.dataOffsetIndexIdentifier))); + + // get the table items and look at the types we are dealing with + Set keySet = table.getItems().keySet(); + Iterator iterator = keySet.iterator(); + + while (iterator.hasNext()) { + Integer key = iterator.next(); + if (key.intValue() >= 0x0001 && + key.intValue() <= 0x0bff) + { + return "Message envelope"; + } + else if (key.intValue() >= 0x1000 && + key.intValue() <= 0x2fff) + { + return "Message content"; + } + else if (key.intValue() >= 0x3400 && + key.intValue() <= 0x35ff) + { + return "Message store"; + } + else if (key.intValue() >= 0x3600 && + key.intValue() <= 0x36ff) + { + return "Folder and address book"; + } + else if (key.intValue() >= 0x3700 && + key.intValue() <= 0x38ff) + { + return "Attachment"; + } + else if (key.intValue() >= 0x3900 && + key.intValue() <= 0x39ff) + { + return "Address book"; + } + else if (key.intValue() >= 0x3a00 && + key.intValue() <= 0x3bff) + { + return "Messaging user"; + } + else if (key.intValue() >= 0x3c00 && + key.intValue() <= 0x3cff) + { + return "Distribution list"; + } + } + return "Unknown"; + } + +} diff --git a/com/pff/objects/PSTRss.java b/com/pff/objects/PSTRss.java new file mode 100644 index 0000000..bec2e93 --- /dev/null +++ b/com/pff/objects/PSTRss.java @@ -0,0 +1,127 @@ +/** + * 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.objects; + +import java.io.IOException; +import java.util.HashMap; + +import com.pff.exceptions.PSTException; +import com.pff.parsing.DescriptorIndexNode; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.tables.PSTTableBC; +import com.pff.source.PSTSource; + +/** + * Object that represents a RSS item + * @author Richard Johnson + */ +public class PSTRss extends PSTMessage { + + /** + * @param theFile + * @param descriptorIndexNode + * @throws PSTException + * @throws IOException + */ + public PSTRss(PSTSource theFile, DescriptorIndexNode descriptorIndexNode) + throws PSTException, IOException { + super(theFile, descriptorIndexNode); + } + + /** + * @param theFile + * @param folderIndexNode + * @param table + * @param localDescriptorItems + */ + public PSTRss(PSTSource theFile, DescriptorIndexNode folderIndexNode, + PSTTableBC table, + HashMap localDescriptorItems) { + super(theFile, folderIndexNode, table, localDescriptorItems); + } + + /** + * Channel + */ + public String getPostRssChannelLink() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008900, PSTSource.PSETID_PostRss)); + } + /** + * Item link + */ + public String getPostRssItemLink() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008901, PSTSource.PSETID_PostRss)); + } + /** + * Item hash Integer 32-bit signed + */ + public int getPostRssItemHash() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008902, PSTSource.PSETID_PostRss)); + } + /** + * Item GUID + */ + public String getPostRssItemGuid() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008903, PSTSource.PSETID_PostRss)); + } + /** + * Channel GUID + */ + public String getPostRssChannel() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008904, PSTSource.PSETID_PostRss)); + } + /** + * Item XML + */ + public String getPostRssItemXml() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008905, PSTSource.PSETID_PostRss)); + } + /** + * Subscription + */ + public String getPostRssSubscription() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008906, PSTSource.PSETID_PostRss)); + } + + public String toString() { + return + "Channel ASCII or Unicode string values: "+ getPostRssChannelLink() + "\n" + + "Item link ASCII or Unicode string values: "+ getPostRssItemLink() + "\n" + + "Item hash Integer 32-bit signed: "+ getPostRssItemHash() + "\n" + + "Item GUID ASCII or Unicode string values: "+ getPostRssItemGuid() + "\n" + + "Channel GUID ASCII or Unicode string values: "+ getPostRssChannel() + "\n" + + "Item XML ASCII or Unicode string values: "+ getPostRssItemXml() + "\n" + + "Subscription ASCII or Unicode string values: "+ getPostRssSubscription(); + } +} diff --git a/com/pff/objects/PSTTask.java b/com/pff/objects/PSTTask.java new file mode 100644 index 0000000..1477aec --- /dev/null +++ b/com/pff/objects/PSTTask.java @@ -0,0 +1,197 @@ +/** + * 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.objects; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; + +import com.pff.exceptions.PSTException; +import com.pff.parsing.DescriptorIndexNode; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.tables.PSTTableBC; +import com.pff.source.PSTSource; + +/** + * Object that represents Task items + * @author Richard Johnson + */ +public class PSTTask extends PSTMessage { + + /** + * @param theFile + * @param descriptorIndexNode + * @throws PSTException + * @throws IOException + */ + public PSTTask(PSTSource theFile, DescriptorIndexNode descriptorIndexNode) + throws PSTException, IOException { + super(theFile, descriptorIndexNode); + } + + /** + * @param theFile + * @param folderIndexNode + * @param table + * @param localDescriptorItems + */ + public PSTTask(PSTSource theFile, DescriptorIndexNode folderIndexNode, + PSTTableBC table, + HashMap localDescriptorItems) { + super(theFile, folderIndexNode, table, localDescriptorItems); + } + + /** + * Status Integer 32-bit signed 0x0 => Not started + */ + public int getTaskStatus() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008101, PSTSource.PSETID_Task)); + } + /** + * Percent Complete Floating point double precision (64-bit) + */ + public double getPercentComplete() { + return getDoubleItem(pstFile.getNameToIdMapItem(0x00008102, PSTSource.PSETID_Task)); + } + /** + * Is team task Boolean + */ + public boolean isTeamTask() { + return getBooleanItem(pstFile.getNameToIdMapItem(0x00008103, PSTSource.PSETID_Task)); + } + + /** + * Date completed Filetime + */ + public Date getTaskDateCompleted() { + return getDateItem(pstFile.getNameToIdMapItem(0x0000810f, PSTSource.PSETID_Task)); + } + /** + * Actual effort in minutes Integer 32-bit signed + */ + public int getTaskActualEffort() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008110, PSTSource.PSETID_Task)); + } + /** + * Total effort in minutes Integer 32-bit signed + */ + public int getTaskEstimatedEffort() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008111, PSTSource.PSETID_Task)); + } + /** + * Task version Integer 32-bit signed FTK: Access count + */ + public int getTaskVersion() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008112, PSTSource.PSETID_Task)); + } + /** + * Complete Boolean + */ + public boolean isTaskComplete() { + return getBooleanItem(pstFile.getNameToIdMapItem(0x0000811c, PSTSource.PSETID_Task)); + } + /** + * Owner ASCII or Unicode string + */ + public String getTaskOwner() { + return getStringItem(pstFile.getNameToIdMapItem(0x0000811f, PSTSource.PSETID_Task)); + } + /** + * Delegator ASCII or Unicode string + */ + public String getTaskAssigner() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008121, PSTSource.PSETID_Task)); + } + /** + * Unknown ASCII or Unicode string + */ + public String getTaskLastUser() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008122, PSTSource.PSETID_Task)); + } + /** + * Ordinal Integer 32-bit signed + */ + public int getTaskOrdinal() { + return this.getIntItem(pstFile.getNameToIdMapItem(0x00008123, PSTSource.PSETID_Task)); + } + /** + * Is recurring Boolean + */ + public boolean isTaskFRecurring() { + return getBooleanItem(pstFile.getNameToIdMapItem(0x00008126, PSTSource.PSETID_Task)); + } + /** + * Role ASCII or Unicode string + */ + public String getTaskRole() { + return getStringItem(pstFile.getNameToIdMapItem(0x00008127, PSTSource.PSETID_Task)); + } + /** + * Ownership Integer 32-bit signed + */ + public int getTaskOwnership() { + return getIntItem(pstFile.getNameToIdMapItem(0x00008129, PSTSource.PSETID_Task)); + } + /** + * Delegation State + */ + public int getAcceptanceState() { + return getIntItem(pstFile.getNameToIdMapItem(0x0000812a, PSTSource.PSETID_Task)); + } + + public String toString() { + return + "Status Integer 32-bit signed 0x0 => Not started [TODO]: "+getTaskStatus()+"\n"+ + "Percent Complete Floating point double precision (64-bit): "+getPercentComplete()+"\n"+ + "Is team task Boolean: "+isTeamTask()+"\n"+ + "Start date Filetime: "+getTaskStartDate()+"\n"+ + "Due date Filetime: "+getTaskDueDate()+"\n"+ + "Date completed Filetime: "+getTaskDateCompleted()+"\n"+ + "Actual effort in minutes Integer 32-bit signed: "+getTaskActualEffort()+"\n"+ + "Total effort in minutes Integer 32-bit signed: "+getTaskEstimatedEffort()+"\n"+ + "Task version Integer 32-bit signed FTK: Access count: "+getTaskVersion()+"\n"+ + "Complete Boolean: "+isTaskComplete()+"\n"+ + "Owner ASCII or Unicode string: "+getTaskOwner()+"\n"+ + "Delegator ASCII or Unicode string: "+getTaskAssigner()+"\n"+ + "Unknown ASCII or Unicode string: "+getTaskLastUser()+"\n"+ + "Ordinal Integer 32-bit signed: "+getTaskOrdinal()+"\n"+ + "Is recurring Boolean: "+isTaskFRecurring()+"\n"+ + "Role ASCII or Unicode string: "+getTaskRole()+"\n"+ + "Ownership Integer 32-bit signed: "+getTaskOwnership()+"\n"+ + "Delegation State: "+getAcceptanceState(); + + + + } +} diff --git a/com/pff/objects/sub/PSTAppointmentRecurrence.java b/com/pff/objects/sub/PSTAppointmentRecurrence.java new file mode 100644 index 0000000..533e173 --- /dev/null +++ b/com/pff/objects/sub/PSTAppointmentRecurrence.java @@ -0,0 +1,316 @@ +/** + * 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.objects.sub; + +/* +import java.text.SimpleDateFormat; +/**/ + +import java.util.Calendar; +import java.util.Date; +import java.util.SimpleTimeZone; + +import com.pff.PSTUtils; +import com.pff.exceptions.PSTAppointmentException; +import com.pff.objects.PSTAppointment; +import com.pff.objects.PSTAttachment; +import com.pff.objects.PSTMessage; + +/** + * Class containing recurrence information for a recurring appointment + * @author Orin Eman + * + * + */ + +public class PSTAppointmentRecurrence { + + // Access methods + + public short getExceptionCount() { + return ExceptionCount; + } + + public PSTAppointmentException getException(int i) { + if ( i < 0 || i >= ExceptionCount ) { + return null; + } + return Exceptions[i]; + } + + public short getCalendarType() { + return CalendarType; + } + + public short getPatternType() { + return PatternType; + } + + public int getPeriod() { + return Period; + } + + public int getPatternSpecific() { + return PatternSpecific; + } + + public int getFirstDOW() { + return FirstDOW; + } + + public int getPatternSpecificNth() { + return PatternSpecificNth; + } + + public int getFirstDateTime() { + return FirstDateTime; + } + + public int getEndType() { + return EndType; + } + + public int getOccurrenceCount() { + return OccurrenceCount; + } + + public int getEndDate() { + return EndDate; + } + + public int getStartTimeOffset() { + return StartTimeOffset; + } + + public PSTTimeZone getTimeZone() { + return RecurrenceTimeZone; + } + + public int getRecurFrequency() { + return RecurFrequency; + } + + public int getSlidingFlag() { + return SlidingFlag; + } + + public int getStartDate() { + return StartDate; + } + + public int getEndTimeOffset() { + return EndTimeOffset; + } + + public PSTAppointmentRecurrence(byte[] recurrencePattern, PSTAppointment appt, PSTTimeZone tz) { + RecurrenceTimeZone = tz; + SimpleTimeZone stz = RecurrenceTimeZone.getSimpleTimeZone(); + + // Read the structure + RecurFrequency = (short)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, 4, 6); + PatternType = (short)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, 6, 8); + CalendarType = (short)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, 8, 10); + FirstDateTime = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, 10, 14); + Period = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, 14, 18); + SlidingFlag = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, 18, 22); + int offset = 22; + if ( PatternType != 0 ) { + PatternSpecific = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + if ( PatternType == 0x0003 || PatternType == 0x000B ) { + PatternSpecificNth = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + } + } + EndType = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + OccurrenceCount = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + FirstDOW = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + + DeletedInstanceCount = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + DeletedInstanceDates = new Calendar[DeletedInstanceCount]; + for ( int i = 0; i < DeletedInstanceCount; ++i ) { + DeletedInstanceDates[i] = PSTUtils.apptTimeToUTC( + (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4), + RecurrenceTimeZone); + offset += 4; +/* + SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); + f.setTimeZone(RecurrenceTimeZone.getSimpleTimeZone()); + System.out.printf("DeletedInstanceDates[%d]: %s\n", i, f.format(DeletedInstanceDates[i].getTime())); +/**/ + } + + ModifiedInstanceCount = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + ModifiedInstanceDates = new Calendar[ModifiedInstanceCount]; + for ( int i = 0; i < ModifiedInstanceCount; ++i ) { + ModifiedInstanceDates[i] = PSTUtils.apptTimeToUTC( + (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4), + RecurrenceTimeZone); + offset += 4; +/* + SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); + f.setTimeZone(RecurrenceTimeZone.getSimpleTimeZone()); + System.out.printf("ModifiedInstanceDates[%d]: %s\n", i, f.format(ModifiedInstanceDates[i].getTime())); +/**/ + } + + StartDate = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + EndDate = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4 + 4; // Skip ReaderVersion2 + + writerVersion2 = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + + StartTimeOffset = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + EndTimeOffset = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4; + ExceptionCount = (short)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+2); + offset += 2; + + // Read exceptions + Exceptions = new PSTAppointmentException[ExceptionCount]; + for ( int i = 0; i < ExceptionCount; ++i ) { + Exceptions[i] = new PSTAppointmentException(recurrencePattern, offset, writerVersion2, appt); + offset += Exceptions[i].getLength(); + } + + if ( (offset + 4) <= recurrencePattern.length ) { + int ReservedBlock1Size = (int)PSTUtils.convertLittleEndianBytesToLong(recurrencePattern, offset, offset+4); + offset += 4 + (ReservedBlock1Size * 4); + } + + // Read extended exception info + for ( int i = 0; i < ExceptionCount; ++i ) { + Exceptions[i].buildExtendedException(recurrencePattern, offset); + offset += Exceptions[i].getExtendedLength(); +/* + Calendar c = PSTObject.apptTimeToUTC(Exceptions[i].getStartDateTime(), RecurrenceTimeZone); + System.out.printf("Exception[%d] start: %s\n", i, FormatUTC(c.getTime())); + c = PSTObject.apptTimeToUTC(Exceptions[i].getEndDateTime(), RecurrenceTimeZone); + System.out.printf("Exception[%d] end: %s\n", i, FormatUTC(c.getTime())); + c = PSTObject.apptTimeToUTC(Exceptions[i].getOriginalStartDate(), RecurrenceTimeZone); + System.out.printf("Exception[%d] original start: %s\n", i, FormatUTC(c.getTime())); +/**/ + } + // Ignore any extra data - see http://msdn.microsoft.com/en-us/library/cc979209(office.12).aspx + + // Get attachments, if any + PSTAttachment[] attachments = new PSTAttachment[appt.getNumberOfAttachments()]; + for ( int i = 0; i < attachments.length; ++i ) { + try { + attachments[i] = appt.getAttachment(i); + } catch (Exception e) { + e.printStackTrace(); + attachments[i] = null; + } + } + + PSTAppointment embeddedMessage = null; + for ( int i = 0; i < ExceptionCount; ++i ) { + try { + // Match up an attachment to this exception... + for ( int iAttachment = 0; iAttachment < attachments.length; ++iAttachment ) { + if ( attachments[iAttachment] != null ) { + PSTMessage message = attachments[iAttachment].getEmbeddedPSTMessage(); + if ( !(message instanceof PSTAppointment) ) { + continue; + } + embeddedMessage = (PSTAppointment)message; + Date replaceTime = embeddedMessage.getRecurrenceBase(); +/* + SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); + f.setTimeZone(stz); + System.out.printf("Attachment[%d] time: %s\n", iAttachment, f.format(replaceTime)); +/**/ + Calendar c = Calendar.getInstance(stz); + c.setTime(replaceTime); + if ( c.get(Calendar.YEAR) == ModifiedInstanceDates[i].get(Calendar.YEAR) && + c.get(Calendar.MONTH) == ModifiedInstanceDates[i].get(Calendar.MONTH) && + c.get(Calendar.YEAR) == ModifiedInstanceDates[i].get(Calendar.YEAR) ) + { +/* System.out.println("\tEmbedded Message matched"); /**/ + + Exceptions[i].setEmbeddedMessage(embeddedMessage); + break; + } + } + } + } catch ( Exception e ) { + e.printStackTrace(); + } + } + + attachments = null; + } + +/* + private String FormatUTC(Date date) { + SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); + f.setTimeZone(PSTTimeZone.utcTimeZone); + return f.format(date); + } +/**/ + + private short RecurFrequency; + private short PatternType; + private short CalendarType; + private int FirstDateTime; + private int Period; + private int SlidingFlag; + private int PatternSpecific; + private int PatternSpecificNth; + private int EndType; + private int OccurrenceCount; + private int FirstDOW; + private int DeletedInstanceCount; + private Calendar[] DeletedInstanceDates = null; + private int ModifiedInstanceCount; + private Calendar[] ModifiedInstanceDates = null; + private int StartDate; + private int EndDate; + //private int readerVersion2; + private int writerVersion2; + private int StartTimeOffset; + private int EndTimeOffset; + private short ExceptionCount; + private PSTAppointmentException[] Exceptions = null; + private PSTTimeZone RecurrenceTimeZone = null; +} diff --git a/com/pff/objects/sub/PSTRecipient.java b/com/pff/objects/sub/PSTRecipient.java new file mode 100644 index 0000000..4e7ebe3 --- /dev/null +++ b/com/pff/objects/sub/PSTRecipient.java @@ -0,0 +1,154 @@ +/** + * 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.objects.sub; + +//import java.util.Date; +import java.util.HashMap; + +import com.pff.parsing.tables.PSTTable7CItem; + +/** + * Class containing recipient information + * @author Orin Eman + * + * + */ +public class PSTRecipient { + private HashMap details; + + + public static final int MAPI_TO = 1; + public static final int MAPI_CC = 2; + public static final int MAPI_BCC = 3; + + public PSTRecipient(HashMap recipientDetails) { + details = recipientDetails; + } + + public String getDisplayName() { + return getString(0x3001); + } + + public int getRecipientType() { + return getInt(0x0c15); + } + + public String getEmailAddressType() { + return getString(0x3002); + } + + public String getEmailAddress() { + return getString(0x3003); + } + + public int getRecipientFlags() { + return getInt(0x5ffd); + } + + public int getRecipientOrder() { + return getInt(0x5fdf); + } + + public String getSmtpAddress() + { + // If the recipient address type is SMTP, + // we can simply return the recipient address. + String addressType = getEmailAddressType(); + if ( addressType!= null && + addressType.equalsIgnoreCase("smtp") ) + { + String addr = getEmailAddress(); + if ( addr != null && addr.length() != 0 ) { + return addr; + } + } + // Otherwise, we have to hope the SMTP address is + // present as the PidTagPrimarySmtpAddress property. + return getString(0x39FE); + } + + private String getString(int id) { + if ( details.containsKey(id) ) { + PSTTable7CItem item = details.get(id); + return item.getStringValue(); + } + + return ""; + } + +/* private boolean getBoolean(int id) { + if ( details.containsKey(id) ) { + PSTTable7CItem item = details.get(id); + if ( item.entryValueType == 0x000B ) + { + return (item.entryValueReference & 0xFF) == 0 ? false : true; + } + } + + return false; + } +*/ + private int getInt(int id) { + if ( details.containsKey(id) ) { + PSTTable7CItem item = details.get(id); + if ( item.entryValueType == 0x0003 ) + { + return item.entryValueReference; + } + + if ( item.entryValueType == 0x0002 ) { + short s = (short)item.entryValueReference; + return s; + } + } + + return 0; + } + +/* private Date getDate(int id) { + long lDate = 0; + + if ( details.containsKey(id) ) { + PSTTable7CItem item = details.get(id); + if ( item.entryValueType == 0x0040 ) { + int high = (int)PSTObject.convertLittleEndianBytesToLong(item.data, 4, 8); + int low = (int)PSTObject.convertLittleEndianBytesToLong(item.data, 0, 4); + + return PSTObject.filetimeToDate(high, low); + } + } + return new Date(lDate); + } +*/ +} diff --git a/com/pff/objects/sub/PSTTimeZone.java b/com/pff/objects/sub/PSTTimeZone.java new file mode 100644 index 0000000..152a424 --- /dev/null +++ b/com/pff/objects/sub/PSTTimeZone.java @@ -0,0 +1,270 @@ +/** + * 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.objects.sub; + +import java.util.Calendar; +import java.util.SimpleTimeZone; + +import com.pff.PSTUtils; + +/** + * Class containing time zone information + * @author Orin Eman + * + * + */ + +public class PSTTimeZone { + + + public PSTTimeZone(byte [] timeZoneData) { + this.rule = null; + name = ""; + + try { + int headerLen = (int)PSTUtils.convertLittleEndianBytesToLong(timeZoneData, 2, 4); + int nameLen = 2*(int)PSTUtils.convertLittleEndianBytesToLong(timeZoneData, 6, 8); + name = new String(timeZoneData, 8, nameLen, "UTF-16LE"); + int ruleOffset = 8+nameLen; + int nRules = (int)PSTUtils.convertLittleEndianBytesToLong(timeZoneData, ruleOffset, ruleOffset+2); + + ruleOffset = 4 + headerLen; + for ( int rule = 0; rule < nRules; ++rule ) { + // Is this rule the effective rule? + int flags = (int)PSTUtils.convertLittleEndianBytesToLong(timeZoneData, ruleOffset+4, ruleOffset+6); + if ( (flags & 0x0002) != 0 ) { + this.rule = new TZRule(timeZoneData, ruleOffset+6); + break; + } + ruleOffset += 66; + } + } + catch ( Exception e ) { + System.err.printf("Exception reading timezone: %s\n", e.toString()); + e.printStackTrace(); + this.rule = null; + name = ""; + } + } + + public PSTTimeZone(String name, byte[] timeZoneData) { + this.name = name; + this.rule = null; + + try { + this.rule = new TZRule(new SYSTEMTIME(), timeZoneData, 0); + } + catch ( Exception e ) { + System.err.printf("Exception reading timezone: %s\n", e.toString()); + e.printStackTrace(); + this.rule = null; + name = ""; + } + } + + public String getName() { + return name; + } + + public SimpleTimeZone getSimpleTimeZone() { + if ( simpleTimeZone != null ) { + return simpleTimeZone; + } + + if ( rule.startStandard.wMonth == 0 ) { + // A time zone with no daylight savings time + simpleTimeZone = new SimpleTimeZone((rule.lBias+rule.lStandardBias) * 60 * 1000, name); + + return simpleTimeZone; + } + + int startMonth = (rule.startDaylight.wMonth -1 ) + Calendar.JANUARY; + int startDayOfMonth = (rule.startDaylight.wDay == 5) ? -1 : ((rule.startDaylight.wDay - 1) * 7) + 1; + int startDayOfWeek = rule.startDaylight.wDayOfWeek + Calendar.SUNDAY; + int endMonth = (rule.startStandard.wMonth -1 ) + Calendar.JANUARY; + int endDayOfMonth = (rule.startStandard.wDay == 5) ? -1 : ((rule.startStandard.wDay - 1) * 7) + 1; + int endDayOfWeek = rule.startStandard.wDayOfWeek + Calendar.SUNDAY; + int savings = (rule.lStandardBias-rule.lDaylightBias) * 60 * 1000; + + simpleTimeZone = new SimpleTimeZone( + -((rule.lBias+rule.lStandardBias) * 60 * 1000), + name, + startMonth, startDayOfMonth, -startDayOfWeek, + (((((rule.startDaylight.wHour * 60) + + rule.startDaylight.wMinute) * 60) + + rule.startDaylight.wSecond) * 1000) + + rule.startDaylight.wMilliseconds, + endMonth, endDayOfMonth, -endDayOfWeek, + (((((rule.startStandard.wHour * 60) + + rule.startStandard.wMinute) * 60) + + rule.startStandard.wSecond) * 1000) + + rule.startStandard.wMilliseconds, + savings + ); + + return simpleTimeZone; + } + + public boolean isEqual(PSTTimeZone rhs) { + if ( name.equalsIgnoreCase(rhs.name) ) { + if ( rule.isEqual(rhs.rule) ) { + return true; + } + + System.err.printf("Warning: different timezones with the same name: %s\n", name); + } + return false; + } + + public SYSTEMTIME getStart() { + return rule.dtStart; + } + + public int getBias() { + return rule.lBias; + } + + public int getStandardBias() { + return rule.lStandardBias; + } + + public int getDaylightBias() { + return rule.lDaylightBias; + } + + public SYSTEMTIME getDaylightStart() { + return rule.startDaylight; + } + + public SYSTEMTIME getStandardStart() { + return rule.startStandard; + } + + public class SYSTEMTIME { + + SYSTEMTIME() { + wYear = 0; + wMonth = 0; + wDayOfWeek = 0; + wDay = 0; + wHour = 0; + wMinute = 0; + wSecond = 0; + wMilliseconds = 0; + } + + SYSTEMTIME(byte[] timeZoneData, int offset) { + wYear = (short)(PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset, offset+2)&0x7FFF); + wMonth = (short)(PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset+2, offset+4)&0x7FFF); + wDayOfWeek = (short)(PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset+4, offset+6)&0x7FFF); + wDay = (short)(PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset+6, offset+8)&0x7FFF); + wHour = (short)(PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset+8, offset+10)&0x7FFF); + wMinute = (short)(PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset+10, offset+12)&0x7FFF); + wSecond = (short)(PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset+12, offset+14)&0x7FFF); + wMilliseconds = (short)(PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset+14, offset+16)&0x7FFF); + } + + boolean isEqual(SYSTEMTIME rhs) { + return wYear == rhs.wYear && + wMonth == rhs.wMonth && + wDayOfWeek == rhs.wDayOfWeek && + wDay == rhs.wDay && + wHour == rhs.wHour && + wMinute == rhs.wMinute && + wSecond == rhs.wSecond && + wMilliseconds == rhs.wMilliseconds; + } + + public short wYear; + public short wMonth; + public short wDayOfWeek; + public short wDay; + public short wHour; + public short wMinute; + public short wSecond; + public short wMilliseconds; + } + + /** + * A static copy of the UTC time zone, available for others to use + */ + public static SimpleTimeZone utcTimeZone = new SimpleTimeZone(0, "UTC"); + + private class TZRule { + + TZRule(SYSTEMTIME dtStart, byte[] timeZoneData, int offset) { + this.dtStart = dtStart; + InitBiases(timeZoneData, offset); + @SuppressWarnings("unused") + short wStandardYear = (short)PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset+12, offset+14); + startStandard = new SYSTEMTIME(timeZoneData, offset+14); + @SuppressWarnings("unused") + short wDaylightYear = (short)PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset+30, offset+32); + startDaylight = new SYSTEMTIME(timeZoneData, offset+32); + } + + TZRule(byte[] timeZoneData, int offset) { + dtStart = new SYSTEMTIME(timeZoneData, offset); + InitBiases(timeZoneData, offset+16); + startStandard = new SYSTEMTIME(timeZoneData, offset+28); + startDaylight = new SYSTEMTIME(timeZoneData, offset+44); + } + + private void InitBiases(byte[] timeZoneData, int offset) { + lBias = (int)PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset, offset+4); + lStandardBias = (int)PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset+4, offset+8); + lDaylightBias = (int)PSTUtils.convertLittleEndianBytesToLong(timeZoneData, offset+8, offset+12); + } + + boolean isEqual(TZRule rhs) { + return dtStart.isEqual(rhs.dtStart) && + lBias == rhs.lBias && + lStandardBias == rhs.lStandardBias && + lDaylightBias == rhs.lDaylightBias && + startStandard.isEqual(rhs.startStandard) && + startDaylight.isEqual(rhs.startDaylight); + } + + SYSTEMTIME dtStart; + int lBias; + int lStandardBias; + int lDaylightBias; + SYSTEMTIME startStandard; + SYSTEMTIME startDaylight; + } + + private String name; + private TZRule rule; + private SimpleTimeZone simpleTimeZone = null; +} diff --git a/com/pff/parsing/DescriptorIndexNode.java b/com/pff/parsing/DescriptorIndexNode.java new file mode 100644 index 0000000..6b13daf --- /dev/null +++ b/com/pff/parsing/DescriptorIndexNode.java @@ -0,0 +1,104 @@ +/** + * 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.parsing; + +import java.io.IOException; + +import com.pff.PSTUtils; +import com.pff.exceptions.PSTException; +import com.pff.source.PSTSource; + +/** + * DescriptorIndexNode is a leaf item from the Descriptor index b-tree + * It is like a pointer to an element in the PST file, everything has one... + * @author Richard Johnson + */ +public class DescriptorIndexNode { + public int descriptorIdentifier; + public long dataOffsetIndexIdentifier; + public long localDescriptorsOffsetIndexIdentifier; + public int parentDescriptorIndexIdentifier; + public int itemType; + + //PSTFile.PSTFileBlock dataBlock = null; + + /** + * parse the data out into something meaningful + * @param data + */ + public DescriptorIndexNode(byte[] data, int pstFileType) { + // parse it out + // first 4 bytes + if (pstFileType == PSTSource.PST_TYPE_ANSI) { + descriptorIdentifier = (int)PSTUtils.convertLittleEndianBytesToLong(data, 0, 4); + dataOffsetIndexIdentifier = (int)PSTUtils.convertLittleEndianBytesToLong(data, 4, 8); + localDescriptorsOffsetIndexIdentifier = (int)PSTUtils.convertLittleEndianBytesToLong(data, 8, 12); + parentDescriptorIndexIdentifier = (int)PSTUtils.convertLittleEndianBytesToLong(data, 12, 16); + //itemType = (int)PSTObject.convertLittleEndianBytesToLong(data, 28, 32); + } else { + descriptorIdentifier = (int)PSTUtils.convertLittleEndianBytesToLong(data, 0, 4); + dataOffsetIndexIdentifier = (int)PSTUtils.convertLittleEndianBytesToLong(data, 8, 16); + localDescriptorsOffsetIndexIdentifier = (int)PSTUtils.convertLittleEndianBytesToLong(data, 16, 24); + parentDescriptorIndexIdentifier = (int)PSTUtils.convertLittleEndianBytesToLong(data, 24, 28); + itemType = (int)PSTUtils.convertLittleEndianBytesToLong(data, 28, 32); + } + } + + /* + void readData(PSTFile file) + throws IOException, PSTException + { + if ( dataBlock == null ) { + dataBlock = file.readLeaf(dataOffsetIndexIdentifier); + } + } + * + */ + + PSTNodeInputStream getNodeInputStream(PSTSource pstFile) + throws IOException, PSTException + { + return new PSTNodeInputStream(pstFile,pstFile.getOffsetIndexNode(dataOffsetIndexIdentifier)); + } + + public String toString() { + + return "DescriptorIndexNode\n" + + "Descriptor Identifier: "+descriptorIdentifier+" (0x"+Long.toHexString(descriptorIdentifier)+")\n"+ + "Data offset identifier: "+dataOffsetIndexIdentifier+" (0x"+Long.toHexString(dataOffsetIndexIdentifier)+")\n"+ + "Local descriptors offset index identifier: "+localDescriptorsOffsetIndexIdentifier+" (0x"+Long.toHexString(localDescriptorsOffsetIndexIdentifier)+")\n"+ + "Parent Descriptor Index Identifier: "+parentDescriptorIndexIdentifier+" (0x"+Long.toHexString(parentDescriptorIndexIdentifier)+")\n"+ + "Item Type: "+itemType+" (0x"+Long.toHexString(itemType)+")"; + } +} diff --git a/com/pff/parsing/OffsetIndexItem.java b/com/pff/parsing/OffsetIndexItem.java new file mode 100644 index 0000000..6e7635c --- /dev/null +++ b/com/pff/parsing/OffsetIndexItem.java @@ -0,0 +1,94 @@ +/** + * 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.parsing; + +import com.pff.PSTUtils; +import com.pff.source.PSTSource; + +/** + * OffsetIndexItem is a leaf item from the Offset index b-tree + * Only really used internally to get the file offset for items + * @author Richard Johnson + */ +public class OffsetIndexItem { + long indexIdentifier; + long fileOffset; + int size; + long cRef; + + public OffsetIndexItem(byte[] data, int pstFileType) { + if (pstFileType == PSTSource.PST_TYPE_ANSI) { + indexIdentifier = PSTUtils.convertLittleEndianBytesToLong(data, 0, 4); + fileOffset = PSTUtils.convertLittleEndianBytesToLong(data, 4, 8); + size = (int)PSTUtils.convertLittleEndianBytesToLong(data, 8, 10); + cRef = (int)PSTUtils.convertLittleEndianBytesToLong(data, 10, 12); + } else { + indexIdentifier = PSTUtils.convertLittleEndianBytesToLong(data, 0, 8); + fileOffset = PSTUtils.convertLittleEndianBytesToLong(data, 8, 16); + size = (int)PSTUtils.convertLittleEndianBytesToLong(data, 16, 18); + cRef = (int)PSTUtils.convertLittleEndianBytesToLong(data, 16, 18); + } + //System.out.println("Data size: "+data.length); + + } + + + @Override + public String toString() { + return "OffsetIndexItem\n"+ + "Index Identifier: "+indexIdentifier+" (0x"+Long.toHexString(indexIdentifier)+")\n"+ + "File Offset: "+fileOffset+" (0x"+Long.toHexString(fileOffset)+")\n"+ + "cRef: "+cRef+" (0x"+Long.toHexString(cRef)+" bin:"+Long.toBinaryString(cRef)+")\n"+ + "Size: "+size+" (0x"+Long.toHexString(size)+")"; + } + + + public long getIndexIdentifier() { + return indexIdentifier; + } + + + public long getFileOffset() { + return fileOffset; + } + + + public int getSize() { + return size; + } + + + + +} diff --git a/com/pff/parsing/PSTDescriptorItem.java b/com/pff/parsing/PSTDescriptorItem.java new file mode 100644 index 0000000..96a861d --- /dev/null +++ b/com/pff/parsing/PSTDescriptorItem.java @@ -0,0 +1,132 @@ +/** + * 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.parsing; + +import java.io.IOException; + +import com.pff.PSTUtils; +import com.pff.exceptions.PSTException; +import com.pff.source.PSTSource; + + +/** + * The descriptor items contain information that describes a PST object. + * This is like extended table entries, usually when the data cannot fit in a traditional table item. + * @author Richard Johnson + */ +public class PSTDescriptorItem +{ + public PSTDescriptorItem(byte[] data, int offset, PSTSource pstFile) { + this.pstFile = pstFile; + + if (pstFile.getPSTFileType() == PSTSource.PST_TYPE_ANSI) { + descriptorIdentifier = (int)PSTUtils.convertLittleEndianBytesToLong(data, offset, offset+4); + offsetIndexIdentifier = ((int)PSTUtils.convertLittleEndianBytesToLong(data, offset+4, offset+8)) + & 0xfffffffe; + subNodeOffsetIndexIdentifier = (int)PSTUtils.convertLittleEndianBytesToLong(data, offset+8, offset+12) + & 0xfffffffe; + } else { + descriptorIdentifier = (int)PSTUtils.convertLittleEndianBytesToLong(data, offset, offset+4); + offsetIndexIdentifier = ((int)PSTUtils.convertLittleEndianBytesToLong(data, offset+8, offset+16)) + & 0xfffffffe; + subNodeOffsetIndexIdentifier = (int)PSTUtils.convertLittleEndianBytesToLong(data, offset+16, offset+24) + & 0xfffffffe; + } + } + + public byte[] getData() throws IOException, PSTException { + if ( dataBlockData != null ) { + return dataBlockData; + } + + PSTNodeInputStream in = pstFile.readLeaf(offsetIndexIdentifier); + byte[] out = new byte[(int)in.length()]; + in.read(out); + dataBlockData = out; + return dataBlockData; + } + + public int[] getBlockOffsets() throws IOException, PSTException { + if ( dataBlockOffsets != null ) { + return dataBlockOffsets; + } + Long[] offsets = pstFile.readLeaf(offsetIndexIdentifier).getBlockOffsets(); + int[] offsetsOut = new int[offsets.length]; + for (int x = 0; x < offsets.length; x++) { + offsetsOut[x] = offsets[x].intValue(); + } + return offsetsOut; + } + + public int getDataSize() throws IOException, PSTException { + return pstFile.getLeafSize(offsetIndexIdentifier); + } + + // Public data + int descriptorIdentifier; + + + int offsetIndexIdentifier; + int subNodeOffsetIndexIdentifier; + + // These are private to ensure that getData()/getBlockOffets() are used + //private PSTFile.PSTFileBlock dataBlock = null; + byte[] dataBlockData = null; + int[] dataBlockOffsets = null; + private PSTSource pstFile; + + @Override + public String toString() { + return + "PSTDescriptorItem\n"+ + " descriptorIdentifier: "+descriptorIdentifier+"\n"+ + " offsetIndexIdentifier: "+offsetIndexIdentifier+"\n"+ + " subNodeOffsetIndexIdentifier: "+subNodeOffsetIndexIdentifier+"\n"; + + + } + + + public int getDescriptorIdentifier() { + return descriptorIdentifier; + } + + public int getSubNodeOffsetIndexIdentifier() { + return subNodeOffsetIndexIdentifier; + } + + + + +} diff --git a/com/pff/parsing/PSTNodeInputStream.java b/com/pff/parsing/PSTNodeInputStream.java new file mode 100644 index 0000000..707126f --- /dev/null +++ b/com/pff/parsing/PSTNodeInputStream.java @@ -0,0 +1,464 @@ +/* + * 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.parsing; + +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedList; + +import com.pff.PSTUtils; +import com.pff.exceptions.PSTException; +import com.pff.source.PSTSource; + +/** + * this input stream basically "maps" an input stream on top of the random access file + * @author richard + */ +public class PSTNodeInputStream extends InputStream { + + //private _RandomAccessPSTSource in; + private PSTSource 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; + + public PSTNodeInputStream(PSTSource pstFile, byte[] attachmentData) { + this.allData = attachmentData; + this.length = this.allData.length; + encrypted = pstFile.getEncryptionType() == PSTSource.ENCRYPTION_TYPE_COMPRESSIBLE; + this.currentBlock = 0; + this.currentLocation = 0; + } + public PSTNodeInputStream(PSTSource pstFile, byte[] attachmentData, boolean encrypted) { + this.allData = attachmentData; + this.encrypted = encrypted; + this.length = this.allData.length; + this.currentBlock = 0; + this.currentLocation = 0; + } + + public PSTNodeInputStream(PSTSource pstFile, PSTDescriptorItem descriptorItem) + throws IOException, PSTException + { + //this.in = pstFile.getFileHandle(); + this.pstFile = pstFile; + this.encrypted = pstFile.getEncryptionType() == PSTSource.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; + + } + + public PSTNodeInputStream(PSTSource pstFile, OffsetIndexItem offsetItem) + throws IOException, PSTException + { + //this.in = pstFile.getFileHandle(); + this.pstFile = pstFile; + this.encrypted = pstFile.getEncryptionType() == PSTSource.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; + + //System.out.println("PSTNodeInputStream - loadFromOffsetItem 0 => seek "+ offsetItem.fileOffset); + + pstFile.getRASource().seek(offsetItem.fileOffset); + byte[] data = new byte[offsetItem.size]; + pstFile.getRASource().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 = PSTUtils.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)PSTUtils.convertLittleEndianBytesToLong(data, 2, 4); + + int arraySize = 8; + if (this.pstFile.getPSTFileType() == PSTSource.PST_TYPE_ANSI) { + arraySize = 4; + } + if (data[1] == 0x2) { + // XXBlock + int offset = 8; + for (int x = 0; x < numberOfEntries; x++) { + long bid = PSTUtils.convertLittleEndianBytesToLong(data, offset, offset+arraySize); + bid &= 0xfffffffe; + // get the details in this block and + OffsetIndexItem offsetItem = this.pstFile.getOffsetIndexNode(bid); + //System.out.println("PSTNodeInputStream - getBlockSkipPoints 0 => seek "+ offsetItem.fileOffset); + + pstFile.getRASource().seek(offsetItem.fileOffset); + byte[] blockData = new byte[offsetItem.size]; + pstFile.getRASource().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 = PSTUtils.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 = PSTUtils.getCompEnc(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 (pstFile.getRASource().position() != pos) { + pstFile.getRASource().seek(pos); + } + + int output = pstFile.getRASource().read(); + if (output < 0) { + return -1; + } + if (this.encrypted) { + output = PSTUtils.getCompEnc(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) { + PSTUtils.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) { + PSTUtils.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); + + //System.out.println("PSTNodeInputStream - read 0 => seek "+ (offset.fileOffset + currentPosInBlock)); + + pstFile.getRASource().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]; + pstFile.getRASource().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]; + pstFile.getRASource().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) { + PSTUtils.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); + + //System.out.println("PSTNodeInputStream - seek 0 => seek "+ newFilePos); + pstFile.getRASource().seek(newFilePos); + + } + + public long seekAndReadLong(long location, int bytes) + throws IOException, PSTException + { + this.seek(location); + byte[] buffer = new byte[bytes]; + this.read(buffer); + return PSTUtils.convertLittleEndianBytesToLong(buffer); + } + + public PSTSource getPSTFile() { + return this.pstFile; + } + +} diff --git a/com/pff/parsing/tables/PSTTable.java b/com/pff/parsing/tables/PSTTable.java new file mode 100644 index 0000000..6892703 --- /dev/null +++ b/com/pff/parsing/tables/PSTTable.java @@ -0,0 +1,274 @@ +/** + * 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.parsing.tables; + +import java.io.IOException; +import java.util.HashMap; + +import com.pff.PSTUtils; +import com.pff.exceptions.PSTException; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.PSTNodeInputStream; + + +/** + * The PST Table is the workhorse of the whole system. + * It allows for an item to be read and broken down into the individual properties that it consists of. + * For most PST Objects, it appears that only 7c and bc table types are used. + * @author Richard Johnson + */ +class PSTTable { + + protected String tableType; + protected byte tableTypeByte; + protected int hidUserRoot; + + protected Long[] arrayBlocks = null; + + // info from the b5 header + protected int sizeOfItemKey; + protected int sizeOfItemValue; + protected int hidRoot; + protected int numberOfKeys = 0; + protected int numberOfIndexLevels = 0; + + private PSTNodeInputStream in; + + //private int[][] rgbiAlloc = null; + //private byte[] data = null; + private HashMap subNodeDescriptorItems = null; + + protected String description = ""; + + protected PSTTable(PSTNodeInputStream in, HashMap subNodeDescriptorItems) + throws PSTException, IOException + { + this.subNodeDescriptorItems = subNodeDescriptorItems; + this.in = in; + + arrayBlocks = in.getBlockOffsets(); + + // the next two bytes should be the table type (bSig) + // 0xEC is HN (Heap-on-Node) + in.seek(0); + byte[] headdata = new byte[4]; + in.read(headdata); + if ((int)headdata[2] != 0xffffffec) { + //System.out.println(in.isEncrypted()); + PSTUtils.decode(headdata); + PSTUtils.printHexFormatted(headdata, true); + throw new PSTException("Unable to parse table, bad table type..."); + } + + tableTypeByte = headdata[3]; + switch ((int)tableTypeByte) { // bClientSig + case 0x7c: // Table Context (TC/HN) + tableType = "7c"; + break; +// case 0x9c: +// tableType = "9c"; +// valid = true; +// break; +// case 0xa5: +// tableType = "a5"; +// valid = true; +// break; +// case 0xac: +// tableType = "ac"; +// valid = true; +// break; +// case 0xFFFFFFb5: // BTree-on-Heap (BTH) +// tableType = "b5"; +// valid = true; +// break; + case 0xffffffbc: + tableType = "bc"; // Property Context (PC/BTH) + break; + default: + throw new PSTException("Unable to parse table, bad table type. Unknown identifier: 0x"+Long.toHexString(headdata[3])); + } + + + hidUserRoot = (int)in.seekAndReadLong(4, 4); // hidUserRoot +/* + System.out.printf("Table %s: hidUserRoot 0x%08X\n", tableType, hidUserRoot); +/**/ + + + // all tables should have a BTHHEADER at hnid == 0x20 + NodeInfo headerNodeInfo = getNodeInfo(0x20); + headerNodeInfo.in.seek(headerNodeInfo.startOffset); + int headerByte = headerNodeInfo.in.read() &0xFF; + if ( headerByte != 0xb5 ) { + headerNodeInfo.in.seek(headerNodeInfo.startOffset); + headerByte = headerNodeInfo.in.read() &0xFF; + headerNodeInfo.in.seek(headerNodeInfo.startOffset); + byte[] tmp = new byte[1024]; + headerNodeInfo.in.read(tmp); + PSTUtils.printHexFormatted(tmp, true); + //System.out.println(PSTObject.compEnc[headerByte]); + throw new PSTException("Unable to parse table, can't find BTHHEADER header information: "+headerByte); + } + + sizeOfItemKey = (int)headerNodeInfo.in.read() & 0xFF; // Size of key in key table + sizeOfItemValue = (int)headerNodeInfo.in.read() & 0xFF; // Size of value in key table + + numberOfIndexLevels = (int)headerNodeInfo.in.read() & 0xFF; + if ( numberOfIndexLevels != 0 ) { + // System.out.println(this.tableType); + // System.out.printf("Table with %d index levels\n", numberOfIndexLevels); + } + //hidRoot = (int)PSTObject.convertLittleEndianBytesToLong(nodeInfo, 4, 8); // hidRoot + hidRoot = (int)headerNodeInfo.seekAndReadLong(4, 4); + //System.out.println(hidRoot); + //System.exit(0); +/* + System.out.printf("Table %s: hidRoot 0x%08X\n", tableType, hidRoot); +/**/ + description += "Table ("+tableType+")\n"+ + "hidUserRoot: "+hidUserRoot+" - 0x"+Long.toHexString(hidUserRoot)+"\n"+ + "Size Of Keys: "+sizeOfItemKey+" - 0x"+Long.toHexString(sizeOfItemKey)+"\n"+ + "Size Of Values: "+sizeOfItemValue+" - 0x"+Long.toHexString(sizeOfItemValue)+"\n"+ + "hidRoot: "+hidRoot+" - 0x"+Long.toHexString(hidRoot)+"\n"; + } + + + protected void releaseRawData() { + subNodeDescriptorItems = null; + } + + + /** + * get the number of items stored in this table. + * @return + */ + public int getRowCount() { + return this.numberOfKeys; + } + + class NodeInfo + { + int startOffset; + int endOffset; + //byte[] data; + PSTNodeInputStream in; + + NodeInfo(int start, int end, PSTNodeInputStream in) + throws PSTException + { + if (start > end) + throw new PSTException(String.format("Invalid NodeInfo parameters: start %1$d is greater than end %2$d", start, end)); + startOffset = start; + endOffset = end; + this.in = in; + //this.data = data; + } + + int length() { + return endOffset - startOffset; + } + + long seekAndReadLong(long offset, int length) + throws IOException, PSTException + { + return this.in.seekAndReadLong(startOffset+offset, length); + } + } + + protected NodeInfo getNodeInfo(int hnid) + throws PSTException, IOException + { + + // Zero-length node? + if ( hnid == 0 ) { + return new NodeInfo(0, 0, this.in); + } + + // Is it a subnode ID? + if ( subNodeDescriptorItems != null && + subNodeDescriptorItems.containsKey(hnid) ) + { + PSTDescriptorItem item = subNodeDescriptorItems.get(hnid); + //byte[] data; + NodeInfo subNodeInfo = null; + + try { + //data = item.getData(); + PSTNodeInputStream subNodeIn = new PSTNodeInputStream(in.getPSTFile(), item); + subNodeInfo = new NodeInfo(0, (int)subNodeIn.length(), subNodeIn); + } catch (IOException e) { + throw new PSTException(String.format("IOException reading subNode: 0x%08X", hnid)); + } + + //return new NodeInfo(0, data.length, data); + return subNodeInfo; + } + + if ( (hnid & 0x1F) != 0 ) { + // Some kind of external node + return null; + } + + int whichBlock = (hnid >>> 16); + if ( whichBlock > this.arrayBlocks.length ) { + // Block doesn't exist! + String err = String.format("getNodeInfo: block doesn't exist! hnid = 0x%08X\n", hnid); + err += String.format("getNodeInfo: block doesn't exist! whichBlock = 0x%08X\n", whichBlock); + err += "\n"+(this.arrayBlocks.length); + throw new PSTException(err); + //return null; + } + + // A normal node in a local heap + int index = (hnid & 0xFFFF) >> 5; + int blockOffset = 0; + if (whichBlock > 0) { + blockOffset = arrayBlocks[whichBlock-1].intValue(); + } + // Get offset of HN page map + int iHeapNodePageMap = (int)in.seekAndReadLong(blockOffset, 2) + blockOffset; + int cAlloc = (int)in.seekAndReadLong(iHeapNodePageMap, 2); + if ( index >= cAlloc+1 ) { + throw new PSTException(String.format("getNodeInfo: node index doesn't exist! nid = 0x%08X\n", hnid)); + //return null; + } + iHeapNodePageMap += (2 * index)+2; + int start = (int)in.seekAndReadLong(iHeapNodePageMap, 2) + blockOffset; + int end = (int)in.seekAndReadLong(iHeapNodePageMap + 2, 2) + blockOffset; + + NodeInfo out = new NodeInfo(start, end, in); + return out; + } + +} diff --git a/com/pff/parsing/tables/PSTTable7C.java b/com/pff/parsing/tables/PSTTable7C.java new file mode 100644 index 0000000..883dd16 --- /dev/null +++ b/com/pff/parsing/tables/PSTTable7C.java @@ -0,0 +1,427 @@ +/** + * 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.parsing.tables; + +/* +import java.io.UnsupportedEncodingException; +/**/ +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import com.pff.exceptions.PSTException; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.PSTNodeInputStream; +import com.pff.source.PSTSource; + +/** + * Specific functions for the 7c table type ("Table Context"). + * This is used for attachments. + * @author Richard Johnson + */ +public class PSTTable7C extends PSTTable { + + private final int BLOCK_SIZE = 8176; + + private List> items = null; + private int numberOfDataSets = 0; + private int cCols = 0; + private int TCI_bm = 0; + private NodeInfo rowNodeInfo = null; + private int TCI_1b = 0; + private int overrideCol = -1; + + public PSTTable7C(PSTNodeInputStream in, HashMap subNodeDescriptorItems) + throws PSTException, java.io.IOException + { + this(in, subNodeDescriptorItems, -1); + } + public PSTTable7C(PSTNodeInputStream in, HashMap subNodeDescriptorItems, int entityToExtract) + throws PSTException, java.io.IOException + { + super(in, subNodeDescriptorItems); + + if (tableTypeByte != 0x7c) + { + //System.out.println(Long.toHexString(this.tableTypeByte)); + throw new PSTException("unable to create PSTTable7C, table does not appear to be a 7c!"); + } + + // TCINFO header is in the hidUserRoot node + //byte[] tcHeaderNode = getNodeInfo(hidUserRoot); + NodeInfo tcHeaderNode = getNodeInfo(hidUserRoot); + int offset = 0; + + // get the TCINFO header information + //int cCols = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+1, offset+2); + cCols = (int)tcHeaderNode.seekAndReadLong(offset+1, 1); + @SuppressWarnings("unused") + //int TCI_4b = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+2, offset+4); + int TCI_4b = (int)tcHeaderNode.seekAndReadLong(offset+2, 2); + @SuppressWarnings("unused") + //int TCI_2b = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+4, offset+6); + int TCI_2b = (int)tcHeaderNode.seekAndReadLong(offset+4, 2); + //int TCI_1b = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+6, offset+8); + TCI_1b = (int)tcHeaderNode.seekAndReadLong(offset+6, 2); + //int TCI_bm = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+8, offset+10); + TCI_bm = (int)tcHeaderNode.seekAndReadLong(offset+8, 2); + //int hidRowIndex = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+10, offset+14); + int hidRowIndex = (int)tcHeaderNode.seekAndReadLong(offset+10, 4); + //int hnidRows = (int)PSTObject.convertLittleEndianBytesToLong(tcHeaderNode, offset+14, offset+18);// was 18 + int hnidRows = (int)tcHeaderNode.seekAndReadLong(offset+14, 4); + // 18..22 hidIndex - deprecated + + // 22... column descriptors + offset += 22; + if ( cCols != 0 ) { + columnDescriptors = new ColumnDescriptor[cCols]; + + for ( int col = 0; col < cCols; ++col ) { + //columnDescriptors[col] = new ColumnDescriptor(tcHeaderNode, offset); + columnDescriptors[col] = new ColumnDescriptor(tcHeaderNode, offset); + //System.out.println("iBit: "+col+" " +columnDescriptors[col].iBit); + if (columnDescriptors[col].id == entityToExtract) { + overrideCol = col; + } + offset += 8; + } + } + + // if we are asking for a specific column, only get that! + if (overrideCol > -1) { + cCols = overrideCol +1; + } + + // Read the key table +/* System.out.printf("Key table:\n"); /**/ + keyMap = new HashMap(); + //byte[] keyTableInfo = getNodeInfo(hidRoot); + NodeInfo keyTableInfo = getNodeInfo(hidRoot); + numberOfKeys = keyTableInfo.length() / (sizeOfItemKey+sizeOfItemValue); + offset = 0; + for (int x = 0; x < numberOfKeys; x++) { + int Context = (int)keyTableInfo.seekAndReadLong(offset, sizeOfItemKey); + offset += sizeOfItemKey; + int RowIndex = (int)keyTableInfo.seekAndReadLong(offset, sizeOfItemValue); + offset += sizeOfItemValue; + keyMap.put(Context, RowIndex); + } + + // Read the Row Matrix + rowNodeInfo = getNodeInfo(hnidRows); + //numberOfDataSets = (rowNodeInfo.endOffset - rowNodeInfo.startOffset) / TCI_bm; + + description += + "Number of keys: "+numberOfKeys+"\n"+ + "Number of columns: "+cCols+"\n"+ + "Row Size: "+TCI_bm+"\n"+ + "hidRowIndex: "+hidRowIndex+"\n"+ + "hnidRows: "+hnidRows+"\n"; + + int numberOfBlocks = rowNodeInfo.length() / BLOCK_SIZE; + int numberOfRowsPerBlock = BLOCK_SIZE / TCI_bm; + @SuppressWarnings("unused") + int blockPadding = BLOCK_SIZE - (numberOfRowsPerBlock * TCI_bm); + numberOfDataSets = (numberOfBlocks * numberOfRowsPerBlock) + ((rowNodeInfo.length() % BLOCK_SIZE) / TCI_bm); + } + + /** + * get all the items parsed out of this table. + * @return + */ + public List> getItems() throws PSTException, IOException { + if ( items == null ) { + items = getItems(-1, -1); + } + return items; + } + + + public List> getItems(int startAtRecord, int numberOfRecordsToReturn) + throws PSTException, IOException + { + List> itemList = new ArrayList>(); + + // okay, work out the number of records we have + int numberOfBlocks = rowNodeInfo.length() / BLOCK_SIZE; + int numberOfRowsPerBlock = BLOCK_SIZE / TCI_bm; + int blockPadding = BLOCK_SIZE - (numberOfRowsPerBlock * TCI_bm); + numberOfDataSets = (numberOfBlocks * numberOfRowsPerBlock) + ((rowNodeInfo.length() % BLOCK_SIZE) / TCI_bm); + + if (startAtRecord == -1) { + numberOfRecordsToReturn = numberOfDataSets; + startAtRecord = 0; + } + + // repeat the reading process for every dataset + int currentValueArrayStart = + ((startAtRecord / numberOfRowsPerBlock) * BLOCK_SIZE) + + ((startAtRecord % numberOfRowsPerBlock) * TCI_bm); + + if (numberOfRecordsToReturn > this.getRowCount() - startAtRecord) { + numberOfRecordsToReturn = this.getRowCount() - startAtRecord; + } + + int dataSetNumber = 0; + //while ( currentValueArrayStart + ((cCols+7)/8) + TCI_1b <= rowNodeInfo.length()) + for (int rowCounter = 0; rowCounter < numberOfRecordsToReturn; rowCounter++) + { + HashMap currentItem = new HashMap(); + // add on some padding for block boundries? + if (rowNodeInfo.in.getPSTFile().getPSTFileType() == PSTSource.PST_TYPE_ANSI) { + if (currentValueArrayStart >= BLOCK_SIZE) { + currentValueArrayStart = currentValueArrayStart + (4) * (currentValueArrayStart / BLOCK_SIZE); + } + if (rowNodeInfo.startOffset+ currentValueArrayStart + TCI_1b > rowNodeInfo.in.length()) { + continue; + } + } else { + if ((currentValueArrayStart % BLOCK_SIZE) > BLOCK_SIZE - TCI_bm) { + // adjust! + //currentValueArrayStart += 8176 - (currentValueArrayStart % 8176); + currentValueArrayStart += blockPadding; + if (currentValueArrayStart + TCI_bm < rowNodeInfo.length()) { + continue; + } + } + } + byte[] bitmap = new byte[(cCols+7)/8]; + //System.arraycopy(rowNodeInfo, currentValueArrayStart+TCI_1b, bitmap, 0, bitmap.length); + rowNodeInfo.in.seek(rowNodeInfo.startOffset+ currentValueArrayStart + TCI_1b); + rowNodeInfo.in.read(bitmap); + + //int id = (int)PSTObject.convertLittleEndianBytesToLong(rowNodeInfo, currentValueArrayStart, currentValueArrayStart+4); + int id = (int)rowNodeInfo.seekAndReadLong(currentValueArrayStart, 4); + + // Put into the item map as PidTagLtpRowId (0x67F2) + PSTTable7CItem item = new PSTTable7CItem(); + item.itemIndex = -1; + item.entryValueType = 3; + item.entryType = 0x67F2; + item.entryValueReference = id; + item.isExternalValueReference = true; + currentItem.put(item.entryType, item); + + int col = 0; + if (overrideCol > -1) { + col = overrideCol; + } + for ( ; col < cCols; ++col ) + { + // Does this column exist for this row? + int bitIndex = columnDescriptors[col].iBit / 8; + int bit = columnDescriptors[col].iBit % 8; + if ( bitIndex >= bitmap.length || (bitmap[bitIndex] & (1< 0 ) { + System.out.println("here"); + System.out.println(rowNodeInfo.length()); + PSTObject.printHexFormatted(rowNodeInfo, true); + System.exit(0); + } + */ + + //item.entryValueReference = (int)PSTObject.convertLittleEndianBytesToLong(rowNodeInfo, currentValueArrayStart+columnDescriptors[col].ibData, currentValueArrayStart+columnDescriptors[col].ibData+4); + item.entryValueReference = (int)rowNodeInfo.seekAndReadLong(currentValueArrayStart+columnDescriptors[col].ibData, 4); + if ( columnDescriptors[col].type == 0x0003 || + columnDescriptors[col].type == 0x0004 || + columnDescriptors[col].type == 0x000A ) { + // True 32bit data + item.isExternalValueReference = true; +/* System.out.printf("\tInteger32: %s %d\n", + PSTFile.getPropertyDescription(item.entryType, item.entryValueType), + item.entryValueReference); /**/ + break; + } + + // Variable length data so it's an hnid + if ( (item.entryValueReference & 0x1F) != 0 ) { + // Some kind of external reference... + item.isExternalValueReference = true; +/* System.out.printf("\tOther: %s 0x%08X\n", + PSTFile.getPropertyDescription(item.entryType, item.entryValueType), item.entryValueReference); /**/ + break; + } + + if ( item.entryValueReference == 0 ) { +/* System.out.printf("\tOther: %s 0 bytes\n", + PSTFile.getPropertyDescription(item.entryType, item.entryValueType)); /**/ + item.data = new byte[0]; + break; + } else { + NodeInfo entryInfo = getNodeInfo(item.entryValueReference); + item.data = new byte[entryInfo.length()]; + //System.arraycopy(entryInfo, 0, item.data, 0, item.data.length); + entryInfo.in.seek(entryInfo.startOffset); + entryInfo.in.read(item.data); + } +/* + if ( item.entryValueType != 0x001F ) { + System.out.printf("\tOther: %s %d bytes\n", + PSTFile.getPropertyDescription(item.entryType, item.entryValueType), + item.data.length); + } else { + try { + String s = new String(item.data, "UTF-16LE"); + System.out.printf("\tString: %s \"%s\"\n", + PSTFile.getPropertyDescription(item.entryType, item.entryValueType), + s); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } +/**/ + break; + } + + currentItem.put(item.entryType, item); + + //description += item.toString()+"\n\n"; + } + itemList.add(dataSetNumber, currentItem); + dataSetNumber++; + currentValueArrayStart += TCI_bm; + } + +// System.out.println(description); + + return itemList; + } + + class ColumnDescriptor { + ColumnDescriptor(NodeInfo nodeInfo, int offset) + throws PSTException, IOException + { + //type = (int)(PSTObject.convertLittleEndianBytesToLong(data, offset, offset+2) & 0xFFFF); + type = ((int)nodeInfo.seekAndReadLong(offset, 2) & 0xFFFF); + //id = (int)(PSTObject.convertLittleEndianBytesToLong(data, offset+2, offset+4) & 0xFFFF); + id = (int)(nodeInfo.seekAndReadLong(offset+2, 2) & 0xFFFF); + //ibData = (int)(PSTObject.convertLittleEndianBytesToLong(data, offset+4, offset+6) & 0xFFFF); + ibData = (int)(nodeInfo.seekAndReadLong(offset+4, 2) & 0xFFFF); + //cbData = (int)data[offset+6] & 0xFF; + cbData = nodeInfo.in.read() & 0xFF; + //iBit = (int)data[offset+7] & 0xFF; + iBit = nodeInfo.in.read() & 0xFF; + } + + int type; + int id; + int ibData; + int cbData; + int iBit; + } + + @Override + public int getRowCount() { + return this.numberOfDataSets; + } +/* Not used... + public HashMap getItem(int itemNumber) { + if ( items == null || itemNumber >= items.size() ) { + return null; + } + + return items.get(itemNumber); + } +/**/ + @Override + public String toString() { + return this.description; + } + + public String getItemsString() { + if ( items == null ) { + return ""; + } + + return items.toString(); + } + + ColumnDescriptor[] columnDescriptors = null; + HashMap keyMap = null; +} diff --git a/com/pff/parsing/tables/PSTTable7CItem.java b/com/pff/parsing/tables/PSTTable7CItem.java new file mode 100644 index 0000000..0d0fec3 --- /dev/null +++ b/com/pff/parsing/tables/PSTTable7CItem.java @@ -0,0 +1,46 @@ +/** + * 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.parsing.tables; + +/** + * Items found in the 7c tables + * @author Richard Johnson + */ +public class PSTTable7CItem extends PSTTableItem +{ + + public String toString() { + return "7c Table Item: " + super.toString() + "\n"; + } +} diff --git a/com/pff/parsing/tables/PSTTableBC.java b/com/pff/parsing/tables/PSTTableBC.java new file mode 100644 index 0000000..22a56aa --- /dev/null +++ b/com/pff/parsing/tables/PSTTableBC.java @@ -0,0 +1,207 @@ +/** + * 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.parsing.tables; + +import java.util.HashMap; + +import com.pff.PSTUtils; +import com.pff.exceptions.PSTException; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.PSTNodeInputStream; + + +/** + * The BC Table type. (Property Context) + * Used by pretty much everything. + * @author Richard Johnson + */ +public class PSTTableBC extends PSTTable { + + private HashMap items = new HashMap(); + + private StringBuilder descBuffer = new StringBuilder(); + private boolean isDescNotYetInitiated = false; + + public PSTTableBC(PSTNodeInputStream in) throws PSTException, java.io.IOException { + super(in, new HashMap()); + //data = null; // No direct access to data! + + + if (tableTypeByte != 0xffffffbc) + { + //System.out.println(Long.toHexString(this.tableTypeByte)); + throw new PSTException("unable to create PSTTableBC, table does not appear to be a bc!"); + } + + // go through each of the entries. + //byte[] keyTableInfo = getNodeInfo(hidRoot); + NodeInfo keyTableInfoNodeInfo = getNodeInfo(hidRoot); + byte[] keyTableInfo = new byte[keyTableInfoNodeInfo.length()]; + keyTableInfoNodeInfo.in.seek(keyTableInfoNodeInfo.startOffset); + keyTableInfoNodeInfo.in.read(keyTableInfo); + + //PSTObject.printHexFormatted(keyTableInfo, true); + //System.out.println(in.length()); + //System.exit(0); + numberOfKeys = keyTableInfo.length / (sizeOfItemKey+sizeOfItemValue); + + descBuffer.append("Number of entries: " + numberOfKeys + "\n"); + + // Read the key table + int offset = 0; + for (int x = 0; x < numberOfKeys; x++) { + + PSTTableBCItem item = new PSTTableBCItem(); + item.itemIndex = x; + item.entryType =(int)PSTUtils.convertLittleEndianBytesToLong(keyTableInfo, offset+0, offset+2); + //item.entryType =(int)in.seekAndReadLong(offset, 2); + item.entryValueType = (int)PSTUtils.convertLittleEndianBytesToLong(keyTableInfo, offset+2, offset+4); + //item.entryValueType = (int)in.seekAndReadLong(offset+2, 2); + item.entryValueReference = (int)PSTUtils.convertLittleEndianBytesToLong(keyTableInfo, offset+4, offset+8); + //item.entryValueReference = (int)in.seekAndReadLong(offset+4, 4); + + // Data is in entryValueReference for all types <= 4 bytes long + switch ( item.entryValueType ) { + + case 0x0002: // 16bit integer + item.entryValueReference &= 0xFFFF; + case 0x0003: // 32bit integer + case 0x000A: // 32bit error code +/* + System.out.printf("Integer%s: 0x%04X:%04X, %d\n", + (item.entryValueType == 0x0002) ? "16" : "32", + item.entryType, item.entryValueType, + item.entryValueReference); +/**/ + case 0x0001: // Place-holder + case 0x0004: // 32bit floating + item.isExternalValueReference = true; + break; + + case 0x000b: // Boolean - a single byte + item.entryValueReference &= 0xFF; +/* + System.out.printf("boolean: 0x%04X:%04X, %s\n", + item.entryType, item.entryValueType, + (item.entryValueReference == 0) ? "false" : "true"); +/**/ + item.isExternalValueReference = true; + break; + + case 0x000D: + default: + // Is it in the local heap? + item.isExternalValueReference = true; // Assume not + //System.out.println(item.entryValueReference); + //byte[] nodeInfo = getNodeInfo(item.entryValueReference); + NodeInfo nodeInfoNodeInfo = getNodeInfo(item.entryValueReference); + if ( nodeInfoNodeInfo == null ) { + // It's an external reference that we don't deal with here. +/* + System.out.printf("%s: %shid 0x%08X\n", + (item.entryValueType == 0x1f || item.entryValueType == 0x1e) ? "String" : "Other", + PSTFile.getPropertyDescription(item.entryType, item.entryValueType), + item.entryValueReference); +/**/ + } else { + // Make a copy of the data + //item.data = new byte[nodeInfo.endOffset-nodeInfo.startOffset]; + byte[] nodeInfo = new byte[nodeInfoNodeInfo.length()]; + nodeInfoNodeInfo.in.seek(nodeInfoNodeInfo.startOffset); + nodeInfoNodeInfo.in.read(nodeInfo); + item.data = nodeInfo; // should be new array, so just use it + //System.arraycopy(nodeInfo.data, nodeInfo.startOffset, item.data, 0, item.data.length); + item.isExternalValueReference = false; +/* + if ( item.entryValueType == 0x1f || + item.entryValueType == 0x1e ) + { + try { +// if ( item.entryType == 0x0037 ) + { + String temp = new String(item.data, item.entryValueType == 0x1E ? "UTF8" : "UTF-16LE"); + System.out.printf("String: 0x%04X:%04X, \"%s\"\n", + item.entryType, item.entryValueType, temp); + } + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + else + { + + System.out.printf("Other: 0x%04X:%04X, %d bytes\n", + item.entryType, item.entryValueType, item.data.length); + + } +/**/ + } + break; + } + + offset = offset + 8; + + items.put(item.entryType, item); + // description += item.toString()+"\n\n"; + + } + + releaseRawData(); + } + + + /** + * get the items parsed out of this table. + * @return + */ + public HashMap getItems() { + return this.items; + } + + public String toString() { + + if (isDescNotYetInitiated) { + isDescNotYetInitiated=false; + + for (Integer curItem:items.keySet()) { + descBuffer.append(items.get(curItem).toString() + "\n\n"); + } +// description += item.toString()+"\n\n"; + } + + + return this.description + descBuffer.toString(); + } +} + diff --git a/com/pff/parsing/tables/PSTTableBCItem.java b/com/pff/parsing/tables/PSTTableBCItem.java new file mode 100644 index 0000000..bf33aa0 --- /dev/null +++ b/com/pff/parsing/tables/PSTTableBCItem.java @@ -0,0 +1,46 @@ +/** + * 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.parsing.tables; + +/** + * Items within the BC Table + * @author Richard Johnson + */ +public class PSTTableBCItem extends PSTTableItem +{ + + public String toString() { + return "Table Item: "+super.toString() + "\n"; + } +} diff --git a/com/pff/parsing/tables/PSTTableItem.java b/com/pff/parsing/tables/PSTTableItem.java new file mode 100644 index 0000000..f660413 --- /dev/null +++ b/com/pff/parsing/tables/PSTTableItem.java @@ -0,0 +1,198 @@ +/** + * 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.parsing.tables; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.SimpleTimeZone; + +import com.pff.PSTUtils; +import com.pff.source.PSTSource; + +/** + * Generic table item. + * Provides some basic string functions + * @author Richard Johnson + */ +public class PSTTableItem { + + public static final int VALUE_TYPE_PT_UNICODE = 0x1f; + public static final int VALUE_TYPE_PT_STRING8 = 0x1e; + public static final int VALUE_TYPE_PT_BIN = 0x102; + + public int itemIndex = 0; + public int entryType = 0; + public int entryValueType = 0; + public int entryValueReference = 0; + public byte[] data = new byte[0]; + public boolean isExternalValueReference = false; + + public long getLongValue() { + if ( this.data.length > 0 ) { + return PSTUtils.convertLittleEndianBytesToLong(data); + } + return -1; + } + + public String getStringValue() { + return getStringValue(entryValueType); + } + + /** + * get a string value of the data + * @param forceString if true, you won't get the hex representation of the data + * @return + */ + public String getStringValue(int stringType) { + + if (stringType == VALUE_TYPE_PT_UNICODE) { + // we are a nice little-endian unicode string. + try { + if (isExternalValueReference ) { + return "External string reference!"; + } + return new String(data, "UTF-16LE"); + } catch (UnsupportedEncodingException e) { + + System.err.println("Error decoding string: " + data.toString()); + return ""; + } + } + + if (stringType == VALUE_TYPE_PT_STRING8 ) { + //System.out.println("Warning! decoding string8 without charset: "+this.entryType + " - "+ Integer.toHexString(this.entryType)); + return new String(data, Charset.forName("UTF-8")); + } + + StringBuffer outputBuffer = new StringBuffer(); +/* + if ( stringType == VALUE_TYPE_PT_BIN) { + int theChar; + for (int x = 0; x < data.length; x++) { + theChar = data[x] & 0xFF; + outputBuffer.append((char)theChar); + } + } + else +/**/ + { + // we are not a normal string, give a hexish sort of output + StringBuffer hexOut = new StringBuffer(); + for (int y = 0; y < data.length; y++) { + int valueChar = (int)data[y] & 0xff; + if (Character.isLetterOrDigit((char)valueChar)) { + outputBuffer.append((char)valueChar); + outputBuffer.append(" "); + } + else + { + outputBuffer.append(". "); + } + String hexValue = Long.toHexString(valueChar); + hexOut.append(hexValue); + hexOut.append(" "); + if (hexValue.length() > 1) { + outputBuffer.append(" "); + } + } + outputBuffer.append("\n"); + outputBuffer.append(" "); + outputBuffer.append(hexOut); + } + + return new String(outputBuffer); + } + + public String toString() { + String ret = PSTSource.getPropertyDescription(entryType, entryValueType); + + if ( entryValueType == 0x000B ) + { + return ret + (entryValueReference == 0 ? "false" : "true"); + } + + if ( isExternalValueReference ) { + // Either a true external reference, or entryValueReference contains the actual data + return ret + String.format("0x%08X (%d)", entryValueReference, entryValueReference); + } + + if ( entryValueType == 0x0005 || + entryValueType == 0x0014 ) { + // 64bit data + if ( data == null ) { + return ret + "no data"; + } + if ( data.length == 8 ) { + long l = PSTUtils.convertLittleEndianBytesToLong(data, 0, 8); + return String.format("%s0x%016X (%d)", ret, l, l); + } else { + return String.format("%s invalid data length: %d", ret, data.length); + } + } + + if ( entryValueType == 0x0040 ) { + // It's a date... + int high = (int)PSTUtils.convertLittleEndianBytesToLong(data, 4, 8); + int low = (int)PSTUtils.convertLittleEndianBytesToLong(data, 0, 4); + + Date d = PSTUtils.filetimeToDate(high, low); + dateFormatter.setTimeZone(utcTimeZone); + return ret + dateFormatter.format(d); + } + + if ( entryValueType == 0x001F ) { + // Unicode string + String s; + try { + s = new String(data, "UTF-16LE"); + } catch (UnsupportedEncodingException e) { + System.err.println("Error decoding string: " + data.toString()); + s = ""; + } + + if ( s.length() >= 2 && s.charAt(0) == 0x0001 ) { + return String.format("%s [%04X][%04X]%s", ret, (short)s.charAt(0), (short)s.charAt(1), s.substring(2)); + } + + return ret + s; + } + + return ret + getStringValue(); + } + + private SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); + private static SimpleTimeZone utcTimeZone = new SimpleTimeZone(0, "UTC"); +} diff --git a/com/pff/source/PSTRandomAccessFile.java b/com/pff/source/PSTRandomAccessFile.java new file mode 100644 index 0000000..fe4cc9f --- /dev/null +++ b/com/pff/source/PSTRandomAccessFile.java @@ -0,0 +1,57 @@ +package com.pff.source; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class PSTRandomAccessFile implements _RandomAccessPSTSource { + + RandomAccessFile raf; + + public PSTRandomAccessFile(File f) throws FileNotFoundException { + this.raf = new RandomAccessFile(f, "r"); + } + + + public PSTRandomAccessFile(String filename) throws FileNotFoundException { + this.raf = new RandomAccessFile(filename, "r"); + } + + + + @Override + public void close() throws IOException { + this.raf.close(); + } + + + @Override + public void seek(long pos) throws IOException { + this.raf.seek(pos); + } + + + @Override + public int read() throws IOException { + return this.raf.read(); + } + + + @Override + public int read(byte[] buffer, int offset, int length) throws IOException { + return this.raf.read(buffer, offset, length); + } + + + @Override + public int read(byte[] b) throws IOException { + return this.raf.read(b); + } + + + @Override + public long position() throws IOException { + return this.raf.getFilePointer(); + } +} diff --git a/com/pff/source/PSTSource.java b/com/pff/source/PSTSource.java new file mode 100644 index 0000000..94b28a1 --- /dev/null +++ b/com/pff/source/PSTSource.java @@ -0,0 +1,918 @@ +/** + * 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.source; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Properties; +import java.util.UUID; + +import com.pff.PSTUtils; +import com.pff.exceptions.PSTException; +import com.pff.objects.PSTFolder; +import com.pff.objects.PSTMessageStore; +import com.pff.parsing.DescriptorIndexNode; +import com.pff.parsing.OffsetIndexItem; +import com.pff.parsing.PSTDescriptorItem; +import com.pff.parsing.PSTNodeInputStream; +import com.pff.parsing.tables.PSTTableBC; +import com.pff.parsing.tables.PSTTableBCItem; +import com.pff.parsing.tables.PSTTableItem; + +/** + * PSTFile is the containing class that allows you to access items within a .pst file. + * Start here, get the root of the folders and work your way down through your items. + * @author Richard Johnson + */ +public class PSTSource { + + public static final int ENCRYPTION_TYPE_NONE = 0; + public static final int ENCRYPTION_TYPE_COMPRESSIBLE = 1; + + private static final int MESSAGE_STORE_DESCRIPTOR_IDENTIFIER = 33; + private static final int ROOT_FOLDER_DESCRIPTOR_IDENTIFIER = 290; + + public static final int PST_TYPE_ANSI = 14; + protected static final int PST_TYPE_ANSI_2 = 15; + public static final int PST_TYPE_UNICODE = 23; + + // Known GUIDs + // Local IDs first + public static final int PS_PUBLIC_STRINGS = 0; + public static final int PSETID_Common = 1; + public static final int PSETID_Address = 2; + public static final int PS_INTERNET_HEADERS = 3; + public static final int PSETID_Appointment = 4; + public static final int PSETID_Meeting = 5; + public static final int PSETID_Log = 6; + public static final int PSETID_Messaging = 7; + public static final int PSETID_Note = 8; + public static final int PSETID_PostRss = 9; + public static final int PSETID_Task = 10; + public static final int PSETID_UnifiedMessaging = 11; + public static final int PS_MAPI = 12; + public static final int PSETID_AirSync = 13; + public static final int PSETID_Sharing = 14; + + // Now the string guids + private static final String guidStrings[] = + { "00020329-0000-0000-C000-000000000046", + "00062008-0000-0000-C000-000000000046", + "00062004-0000-0000-C000-000000000046", + "00020386-0000-0000-C000-000000000046", + "00062002-0000-0000-C000-000000000046", + "6ED8DA90-450B-101B-98DA-00AA003F1305", + "0006200A-0000-0000-C000-000000000046", + "41F28F13-83F4-4114-A584-EEDB5A6B0BFF", + "0006200E-0000-0000-C000-000000000046", + "00062041-0000-0000-C000-000000000046", + "00062003-0000-0000-C000-000000000046", + "4442858E-A9E3-4E80-B900-317A210CC15B", + "00020328-0000-0000-C000-000000000046", + "71035549-0739-4DCB-9163-00F0580DBBDF", + "00062040-0000-0000-C000-000000000046" }; + + private HashMap guidMap = new HashMap(); + + // the type of encryption the files uses. + private int encryptionType = 0; + + // our all important tree. + private LinkedHashMap> childrenDescriptorTree = null; + + private HashMap nameToId = new HashMap(); + private HashMap stringToId = new HashMap(); + private static HashMap idToName = new HashMap(); + private HashMap idToString = new HashMap(); + private byte[] guids = null; + + _RandomAccessPSTSource in; + + int itemCount=0; + + /** + * constructor + * @param fileName + * @throws FileNotFoundException + * @throws PSTException + * @throws IOException + */ + public PSTSource(_RandomAccessPSTSource in) + throws FileNotFoundException, PSTException, IOException + { + // attempt to open the file. + this.in = in; + + // get the first 4 bytes, should be !BDN + try { + byte[] temp = new byte[4]; + in.read(temp); + String strValue = new String(temp); + if (!strValue.equals("!BDN")) { + throw new PSTException("Invalid file header: "+strValue+", expected: !BDN"); + } + + // make sure we are using a supported version of a PST... + byte[] fileTypeBytes = new byte[2]; + in.seek(10); //seekStream(in, 10); // + in.read(fileTypeBytes); + // ANSI file types can be 14 or 15: + if (fileTypeBytes[0] == PSTSource.PST_TYPE_ANSI_2) { + fileTypeBytes[0] = PSTSource.PST_TYPE_ANSI; + } + if (fileTypeBytes[0] != PSTSource.PST_TYPE_ANSI && + fileTypeBytes[0] != PSTSource.PST_TYPE_UNICODE) + { + throw new PSTException("Unrecognised PST File version: "+fileTypeBytes[0]); + } + this.pstFileType = fileTypeBytes[0]; + + // make sure encryption is turned off at this stage... + if (this.getPSTFileType() == PST_TYPE_ANSI) { + in.seek(461); + } else { + in.seek(513); + } + encryptionType = in.read(); + if (encryptionType == 0x02) { + throw new PSTException("Only unencrypted and compressable PST files are supported at this time"); + } + + // build out name to id map. + processNameToIdMap(in); + + } catch (IOException err) { + throw new PSTException("Unable to read PST Sig", err); + } + + } + + private int pstFileType = 0; + public int getPSTFileType() { + return pstFileType; + } + + /** + * read the name-to-id map from the file and load it in + * @param in + * @throws IOException + * @throws PSTException + */ + private void processNameToIdMap(_RandomAccessPSTSource in) + throws IOException, PSTException + { + + // Create our guid map + for ( int i = 0; i < guidStrings.length; ++i ) { + UUID uuid = UUID.fromString(guidStrings[i]); + guidMap.put(uuid, i); +/* + System.out.printf("guidMap[{%s}] = %d\n", uuid.toString(), i); +/**/ + } + + // process the name to id map + DescriptorIndexNode nameToIdMapDescriptorNode = (getDescriptorIndexNode(97)); + //nameToIdMapDescriptorNode.readData(this); + + // get the descriptors if we have them + HashMap localDescriptorItems = null; + if (nameToIdMapDescriptorNode.localDescriptorsOffsetIndexIdentifier != 0) { + //PSTDescriptor descriptor = new PSTDescriptor(this, nameToIdMapDescriptorNode.localDescriptorsOffsetIndexIdentifier); + //localDescriptorItems = descriptor.getChildren(); + localDescriptorItems = this.getPSTDescriptorItems(nameToIdMapDescriptorNode.localDescriptorsOffsetIndexIdentifier); + } + + // process the map + //PSTTableBC bcTable = new PSTTableBC(nameToIdMapDescriptorNode.dataBlock.data, nameToIdMapDescriptorNode.dataBlock.blockOffsets); + OffsetIndexItem off = this.getOffsetIndexNode(nameToIdMapDescriptorNode.dataOffsetIndexIdentifier); + PSTNodeInputStream nodein = new PSTNodeInputStream(this, off); + byte[] tmp = new byte[1024]; + nodein.read(tmp); + PSTTableBC bcTable = new PSTTableBC(nodein); + + HashMap tableItems = (bcTable.getItems()); + // Get the guids + PSTTableBCItem guidEntry = tableItems.get(2); // PidTagNameidStreamGuid + guids = getData(guidEntry, localDescriptorItems); + int nGuids = guids.length / 16; + UUID[] uuidArray = new UUID[nGuids]; + int[] uuidIndexes = new int[nGuids]; + int offset = 0; + for ( int i = 0; i < nGuids; ++i ) { + long mostSigBits = (PSTUtils.convertLittleEndianBytesToLong(guids, offset, offset+4) << 32) | + (PSTUtils.convertLittleEndianBytesToLong(guids, offset+4, offset+6) << 16) | + PSTUtils.convertLittleEndianBytesToLong(guids, offset+6, offset+8); + long leastSigBits = PSTUtils.convertBigEndianBytesToLong(guids, offset+8, offset+16); + uuidArray[i] = new UUID(mostSigBits, leastSigBits); + if ( guidMap.containsKey(uuidArray[i]) ) { + uuidIndexes[i] = guidMap.get(uuidArray[i]); + } else { + uuidIndexes[i] = -1; // We don't know this guid + } +/* + System.out.printf("uuidArray[%d] = {%s},%d\n", i, uuidArray[i].toString(), uuidIndexes[i]); +/**/ + offset += 16; + } + + // if we have a reference to an internal descriptor + PSTTableBCItem mapEntries = tableItems.get(3); // + byte[] nameToIdByte = getData(mapEntries, localDescriptorItems); + + PSTTableBCItem stringMapEntries = tableItems.get(4); // + byte[] stringNameToIdByte = getData(stringMapEntries, localDescriptorItems); + + // process the entries + for (int x = 0; x+8 < nameToIdByte.length; x += 8) { + int dwPropertyId = (int)PSTUtils.convertLittleEndianBytesToLong(nameToIdByte, x, x+4); + int wGuid = (int)PSTUtils.convertLittleEndianBytesToLong(nameToIdByte, x+4, x+6); + int wPropIdx = ((int)PSTUtils.convertLittleEndianBytesToLong(nameToIdByte, x+6, x+8)); + + if ( (wGuid & 0x0001) == 0 ) { + wPropIdx += 0x8000; + wGuid >>= 1; + int guidIndex; + if ( wGuid == 1 ) { + guidIndex = PS_MAPI; + } else if ( wGuid == 2 ) { + guidIndex = PS_PUBLIC_STRINGS; + } else { + guidIndex = uuidIndexes[wGuid-3]; + } + nameToId.put((long)dwPropertyId | ((long)guidIndex << 32), wPropIdx); + idToName.put(wPropIdx, (long)dwPropertyId); +/* + System.out.printf("0x%08X:%04X, 0x%08X\n", dwPropertyId, guidIndex, wPropIdx); +/**/ + } else { + // else the identifier is a string + // dwPropertyId becomes thHke byte offset into the String stream in which the string name of the property is stored. + int len = (int)PSTUtils.convertLittleEndianBytesToLong( + stringNameToIdByte, + dwPropertyId, + dwPropertyId+4 + ); + byte[] keyByteValue = new byte[len]; + System.arraycopy(stringNameToIdByte, dwPropertyId+4, keyByteValue, 0, keyByteValue.length); + wPropIdx += 0x8000; + String key = new String(keyByteValue, "UTF-16LE"); + stringToId.put(key, wPropIdx); + idToString.put(wPropIdx, key); + /* + if (wPropIdx == 32784) { + System.out.println("here!" + dwPropertyId); + System.out.println(key); + //System.out.println(32784 - 0x8000); + } + */ + } + } + } + + + private byte [] getData(PSTTableItem item, HashMap localDescriptorItems) + throws IOException, PSTException + { + if ( item.data.length != 0 ) { + return item.data; + } + + if ( localDescriptorItems == null ) { + throw new PSTException("External reference but no localDescriptorItems in PSTFile.getData()"); + } + + if ( item.entryValueType != 0x0102 ) { + throw new PSTException("Attempting to get non-binary data in PSTFile.getData()"); + } + + PSTDescriptorItem mapDescriptorItem = localDescriptorItems.get(item.entryValueReference); + if (mapDescriptorItem == null) { + throw new PSTException ("not here "+item.entryValueReference + "\n"+localDescriptorItems.keySet()); + } + return mapDescriptorItem.getData(); + } + + public int getNameToIdMapItem(int key, int propertySetIndex) + { + long lKey = ((long)propertySetIndex << 32) | (long)key; + Integer i = nameToId.get(lKey); + if ( i == null ) + { + return -1; + } + return i; + } + public int getPublicStringToIdMapItem(String key) + { + Integer i = this.stringToId.get(key); + if (i == null) { + return -1; + } + return i; + } + + + static long getNameToIdMapKey(int id) + //throws PSTException + { + Long i = idToName.get(id); + if ( i == null ) + { + //throw new PSTException("Name to Id mapping not found"); + return -1; + } + return i; + } + + + + static private Properties propertyInternetCodePages = null; + static private boolean bCPFirstTime = true; + public static String getInternetCodePageCharset(int propertyId) { + if ( bCPFirstTime ) { + bCPFirstTime = false; + propertyInternetCodePages = new Properties(); + try { + InputStream propertyStream = PSTSource.class.getResourceAsStream("/InternetCodepages.txt"); + if ( propertyStream != null ) { + propertyInternetCodePages.load(propertyStream); + } else { + propertyInternetCodePages = null; + } + } catch (FileNotFoundException e) { + propertyInternetCodePages = null; + e.printStackTrace(); + } catch (IOException e) { + propertyInternetCodePages = null; + e.printStackTrace(); + } + } + if ( propertyInternetCodePages != null ) { + return propertyInternetCodePages.getProperty(propertyId+""); + } + return null; + } + + + static private Properties propertyNames = null; + static private boolean bFirstTime = true; + + static String getPropertyName(int propertyId, boolean bNamed) { + if ( bFirstTime ) { + bFirstTime = false; + propertyNames = new Properties(); + try { + InputStream propertyStream = PSTSource.class.getResourceAsStream("/PropertyNames.txt"); + if ( propertyStream != null ) { + propertyNames.load(propertyStream); + } else { + propertyNames = null; + } + } catch (FileNotFoundException e) { + propertyNames = null; + e.printStackTrace(); + } catch (IOException e) { + propertyNames = null; + e.printStackTrace(); + } + } + + if ( propertyNames != null ) { + String key = String.format((bNamed ? "%08X" : "%04X"), propertyId); + return propertyNames.getProperty(key); + } + + return null; + } + + + + public static String getPropertyDescription(int entryType, int entryValueType) { + String ret = ""; + if ( entryType < 0x8000 ) { + String name = PSTSource.getPropertyName(entryType, false); + if ( name != null ) { + ret = String.format("%s:%04X: ", name, entryValueType); + } else { + ret = String.format("0x%04X:%04X: ", entryType, entryValueType); + } + } else { + long type = PSTSource.getNameToIdMapKey(entryType); + if ( type == -1 ) { + ret = String.format("0xFFFF(%04X):%04X: ", entryType, entryValueType); + } else { + String name = PSTSource.getPropertyName((int)type, true); + if ( name != null ) { + ret = String.format("%s(%04X):%04X: ", name, entryType, entryValueType); + } else { + ret = String.format("0x%04X(%04X):%04X: ", type, entryType, entryValueType); + } + } + } + return ret; + } + + /** + * destructor just closes the file handle... + */ + @Override + protected void finalize() throws IOException { + in.close(); + } + + /** + * get the type of encryption the file uses + * @return encryption type used in the PST File + */ + public int getEncryptionType() { + return this.encryptionType; + } + + /** + * get the handle to the file we are currently accessing + */ + /*public RandomAccessFile getFileHandle() { + return this.in; + }*/ + + + /** + * get the message store of the PST file. + * Note that this doesn't really have much information, better to look under the root folder + * @throws PSTException + * @throws IOException + */ + public PSTMessageStore getMessageStore() throws PSTException, IOException { + DescriptorIndexNode messageStoreDescriptor = getDescriptorIndexNode(MESSAGE_STORE_DESCRIPTOR_IDENTIFIER); + return new PSTMessageStore(this, messageStoreDescriptor); + } + + /** + * get the root folder for the PST file. + * You should find all of your data under here... + * @throws PSTException + * @throws IOException + */ + public PSTFolder getRootFolder() throws PSTException, IOException { + DescriptorIndexNode rootFolderDescriptor = getDescriptorIndexNode(ROOT_FOLDER_DESCRIPTOR_IDENTIFIER); + PSTFolder output = new PSTFolder(this, rootFolderDescriptor); + return output; + } + + + + public PSTNodeInputStream readLeaf(long bid) throws IOException, PSTException { + //PSTFileBlock ret = null; + //PSTNodeInputStream ret = null; + + // get the index node for the descriptor index + OffsetIndexItem offsetItem = getOffsetIndexNode(bid); + return new PSTNodeInputStream(this, offsetItem); + + } + + + public int getLeafSize(long bid) throws IOException, PSTException { + OffsetIndexItem offsetItem = getOffsetIndexNode(bid); + + // Internal block? + if ( (offsetItem.getIndexIdentifier() & 0x02) == 0 ) { + // No, return the raw size + return offsetItem.getSize(); + } + + // we only need the first 8 bytes + byte[] data = new byte[8]; + in.seek(offsetItem.getFileOffset()); + in.read(data); + + // we are an array, get the sum of the sizes... + return (int)PSTUtils.convertLittleEndianBytesToLong(data, 4, 8); + } + + /** + * Read a file offset from the file + * PST Files have this tendency to store file offsets (pointers) in 8 little endian bytes. + * Convert this to a long for seeking to. + * @param in handle for PST file + * @param startOffset where to read the 8 bytes from + * @return long representing the read location + * @throws IOException + */ + protected long extractLEFileOffset(long startOffset) + throws IOException + { + long offset = 0; + if (this.getPSTFileType() == PSTSource.PST_TYPE_ANSI) { + in.seek(startOffset); + byte[] temp = new byte[4]; + in.read(temp); + offset |= temp[3] & 0xff; + offset <<= 8; + offset |= temp[2] & 0xff; + offset <<= 8; + offset |= temp[1] & 0xff; + offset <<= 8; + offset |= temp[0] & 0xff; + } else { + in.seek(startOffset); + byte[] temp = new byte[8]; + in.read(temp); + offset = temp[7] & 0xff; + long tmpLongValue; + for (int x = 6; x >= 0; x--) { + offset = offset << 8; + tmpLongValue = (long)temp[x] & 0xff; + offset |= tmpLongValue; + } + } + + return offset; + } + + /** + * Generic function used by getOffsetIndexNode and getDescriptorIndexNode for navigating the PST B-Trees + * @param in + * @param index + * @param descTree + * @return + * @throws IOException + * @throws PSTException + */ + private byte[] findBtreeItem(_RandomAccessPSTSource in, long index, boolean descTree) + throws IOException, PSTException + { + + long btreeStartOffset; + // first find the starting point for the offset index + if (this.getPSTFileType() == PST_TYPE_ANSI) { + btreeStartOffset = this.extractLEFileOffset(196); + if (descTree) { + btreeStartOffset = this.extractLEFileOffset(188); + } + } else { + btreeStartOffset = this.extractLEFileOffset(240); + if (descTree) { + btreeStartOffset = this.extractLEFileOffset(224); + } + } + + // okay, what we want to do is navigate the tree until you reach the bottom.... + // try and read the index b-tree + byte[] temp = new byte[2]; + if (this.getPSTFileType() == PST_TYPE_ANSI) { + in.seek(btreeStartOffset+500); + } else { + in.seek(btreeStartOffset+496); + } + in.read(temp); + while ((temp[0] == 0xffffff80 && temp[1] == 0xffffff80 && !descTree) || + (temp[0] == 0xffffff81 && temp[1] == 0xffffff81 && descTree)) + { + + // get the rest of the data.... + byte[] branchNodeItems; + if (this.getPSTFileType() == PST_TYPE_ANSI) { + branchNodeItems = new byte[496]; + } else { + branchNodeItems = new byte[488]; + } + in.seek(btreeStartOffset); + in.read(branchNodeItems); + + int numberOfItems = in.read(); + in.read(); // maxNumberOfItems + in.read(); // itemSize + int levelsToLeaf = in.read(); + + if (levelsToLeaf > 0) { + boolean found = false; + for (int x = 0; x < numberOfItems; x++) { + if (this.getPSTFileType() == PST_TYPE_ANSI) { + long indexIdOfFirstChildNode = extractLEFileOffset(btreeStartOffset + (x * 12)); + if (indexIdOfFirstChildNode > index) { + // get the address for the child first node in this group + btreeStartOffset = extractLEFileOffset(btreeStartOffset+((x-1) * 12)+8); + in.seek(btreeStartOffset+500); + in.read(temp); + found = true; + break; + } + } else { + long indexIdOfFirstChildNode = extractLEFileOffset(btreeStartOffset + (x * 24)); + if (indexIdOfFirstChildNode > index) { + // get the address for the child first node in this group + btreeStartOffset = extractLEFileOffset(btreeStartOffset+((x-1) * 24)+16); + in.seek(btreeStartOffset+496); + in.read(temp); + found = true; + break; + } + } + } + if (!found) { + // it must be in the very last branch... + if (this.getPSTFileType() == PST_TYPE_ANSI) { + btreeStartOffset = extractLEFileOffset(btreeStartOffset+((numberOfItems-1) * 12)+8); + in.seek(btreeStartOffset+500); + in.read(temp); + } else { + btreeStartOffset = extractLEFileOffset(btreeStartOffset+((numberOfItems-1) * 24)+16); + in.seek(btreeStartOffset+496); + in.read(temp); + } + } + } + else + { + // we are at the bottom of the tree... + // we want to get our file offset! + for (int x = 0; x < numberOfItems; x++) { + + if (this.getPSTFileType() == PSTSource.PST_TYPE_ANSI) { + if (descTree) + { + // The 32-bit descriptor index b-tree leaf node item + in.seek(btreeStartOffset + (x * 16)); + temp = new byte[4]; + in.read(temp); + if (PSTUtils.convertLittleEndianBytesToLong(temp) == index) { + // give me the offset index please! + in.seek(btreeStartOffset + (x * 16)); + temp = new byte[16]; + in.read(temp); + return temp; + } + } + else + { + // The 32-bit (file) offset index item + long indexIdOfFirstChildNode = extractLEFileOffset(btreeStartOffset + (x * 12)); + + if (indexIdOfFirstChildNode == index) { + // we found it!!!! OMG + //System.out.println("item found as item #"+x); + in.seek(btreeStartOffset + (x * 12)); + + temp = new byte[12]; + in.read(temp); + return temp; + } + } + } else { + if (descTree) + { + // The 64-bit descriptor index b-tree leaf node item + in.seek(btreeStartOffset + (x * 32)); + + temp = new byte[4]; + in.read(temp); + if (PSTUtils.convertLittleEndianBytesToLong(temp) == index) { + // give me the offset index please! + in.seek(btreeStartOffset + (x * 32)); + temp = new byte[32]; + in.read(temp); + return temp; + } + } + else + { + // The 64-bit (file) offset index item + long indexIdOfFirstChildNode = extractLEFileOffset(btreeStartOffset + (x * 24)); + + if (indexIdOfFirstChildNode == index) { + // we found it!!!! OMG + //System.out.println("item found as item #"+x); + in.seek(btreeStartOffset + (x * 24)); + + temp = new byte[24]; + in.read(temp); + return temp; + } + } + } + } + throw new PSTException("Unable to find "+index); + } + } + + throw new PSTException("Unable to find node: "+index); + } + + /** + * navigate the internal descriptor B-Tree and find a specific item + * @param in + * @param identifier + * @return the descriptor node for the item + * @throws IOException + * @throws PSTException + */ + public DescriptorIndexNode getDescriptorIndexNode(long identifier) + throws IOException, PSTException + { + return new DescriptorIndexNode(findBtreeItem(in, identifier, true), this.getPSTFileType()); + } + + /** + * navigate the internal index B-Tree and find a specific item + * @param in + * @param identifier + * @return the offset index item + * @throws IOException + * @throws PSTException + */ + public OffsetIndexItem getOffsetIndexNode(long identifier) throws IOException, PSTException { + return new OffsetIndexItem(findBtreeItem(in, identifier, false), this.getPSTFileType()); + } + + + /** + * parse a PSTDescriptor and get all of its items + */ + public HashMap getPSTDescriptorItems(long localDescriptorsOffsetIndexIdentifier) + throws PSTException, IOException + { + return this.getPSTDescriptorItems(this.readLeaf(localDescriptorsOffsetIndexIdentifier)); + } + HashMap getPSTDescriptorItems(PSTNodeInputStream in) + throws PSTException, IOException + { + // make sure the signature is correct + in.seek(0); + int sig = in.read(); + if (sig != 0x2) { + throw new PSTException("Unable to process descriptor node, bad signature: "+sig); + } + + HashMap output = new HashMap(); + int numberOfItems = (int)in.seekAndReadLong(2, 2); + int offset; + if (this.getPSTFileType() == PSTSource.PST_TYPE_ANSI) { + offset = 4; + } else { + offset = 8; + } + + byte[] data = new byte[(int)in.length()]; + in.seek(0); + in.read(data); + + for (int x = 0; x < numberOfItems; x++) { + PSTDescriptorItem item = new PSTDescriptorItem(data, offset, this); + output.put(item.getDescriptorIdentifier(), item); + if (this.getPSTFileType() == PSTSource.PST_TYPE_ANSI) { + offset += 12; + } else { + offset += 24; + } + } + + return output; + } + + /** + * Build the children descriptor tree + * This goes through the entire descriptor B-Tree and adds every item to the childrenDescriptorTree. + * This is used as fallback when the nodes that list file contents are broken. + * @param in + * @throws IOException + * @throws PSTException + */ + public LinkedHashMap> getChildDescriptorTree() + throws IOException, PSTException + { + if (this.childrenDescriptorTree == null) { + long btreeStartOffset = 0; + if (this.getPSTFileType() == PST_TYPE_ANSI) { + btreeStartOffset = this.extractLEFileOffset(188); + } else { + btreeStartOffset = this.extractLEFileOffset(224); + } + this.childrenDescriptorTree = new LinkedHashMap>(); + processDescriptorBTree(btreeStartOffset); + } + return this.childrenDescriptorTree; + } + + /** + * Recursive function for building the descriptor tree, used by buildDescriptorTree + * @param in + * @param btreeStartOffset + * @throws IOException + * @throws PSTException + */ + private void processDescriptorBTree(long btreeStartOffset) + throws IOException, PSTException + { + byte[] temp = new byte[2]; + if (this.getPSTFileType() == PST_TYPE_ANSI) { + in.seek(btreeStartOffset+500); + } else { + in.seek(btreeStartOffset+496); + } + in.read(temp); + + if ((temp[0] == 0xffffff81 && temp[1] == 0xffffff81)) { + + if (this.getPSTFileType() == PST_TYPE_ANSI) { + in.seek(btreeStartOffset+496); + } else { + in.seek(btreeStartOffset+488); + } + + int numberOfItems = in.read(); + in.read(); // maxNumberOfItems + in.read(); // itemSize + int levelsToLeaf = in.read(); + + if (levelsToLeaf > 0) { + for (int x = 0; x < numberOfItems; x++) { + if (this.getPSTFileType() == PST_TYPE_ANSI) { + long branchNodeItemStartIndex = (btreeStartOffset + (12*x)); + long nextLevelStartsAt = this.extractLEFileOffset(branchNodeItemStartIndex+8); + processDescriptorBTree(nextLevelStartsAt); + } else { + long branchNodeItemStartIndex = (btreeStartOffset + (24*x)); + long nextLevelStartsAt = this.extractLEFileOffset(branchNodeItemStartIndex+16); + processDescriptorBTree(nextLevelStartsAt); + } + } + } else { + for (int x = 0; x < numberOfItems; x++) { + // The 64-bit descriptor index b-tree leaf node item give me the offset index please! + if (this.getPSTFileType() == PSTSource.PST_TYPE_ANSI) { + in.seek(btreeStartOffset + (x * 16)); + temp = new byte[16]; + in.read(temp); + } else { + in.seek(btreeStartOffset + (x * 32)); + temp = new byte[32]; + in.read(temp); + } + + DescriptorIndexNode tempNode = new DescriptorIndexNode(temp, this.getPSTFileType()); + + // we don't want to be children of ourselves... + if (tempNode.parentDescriptorIndexIdentifier == tempNode.descriptorIdentifier) { + // skip! + } else if (childrenDescriptorTree.containsKey(tempNode.parentDescriptorIndexIdentifier)) { + // add this entry to the existing list of children + LinkedList children = childrenDescriptorTree.get(tempNode.parentDescriptorIndexIdentifier); + children.add(tempNode); + } else { + // create a new entry and add this one to that + LinkedList children = new LinkedList(); + children.add(tempNode); + childrenDescriptorTree.put(tempNode.parentDescriptorIndexIdentifier, children); + } + this.itemCount++; + } + } + } else { + PSTUtils.printHexFormatted(temp, true); + throw new PSTException("Unable to read descriptor node, is not a descriptor"); + } + } + + + public _RandomAccessPSTSource getRASource() { + return this.in; + } + + +} diff --git a/com/pff/source/_RandomAccessPSTSource.java b/com/pff/source/_RandomAccessPSTSource.java new file mode 100644 index 0000000..8fa11d1 --- /dev/null +++ b/com/pff/source/_RandomAccessPSTSource.java @@ -0,0 +1,22 @@ +package com.pff.source; + +import java.io.Closeable; +import java.io.IOException; + +public interface _RandomAccessPSTSource extends Closeable { + + + public void seek(long pos) throws IOException; + + public int read() throws IOException; + + public int read(byte[] buffer, int offset, int length) throws IOException; + + public int read( byte[] b ) throws IOException; + + public long position() throws IOException; + + + + +} diff --git a/deprecated/LZFu.java b/deprecated/LZFu.java new file mode 100644 index 0000000..48f387e --- /dev/null +++ b/deprecated/LZFu.java @@ -0,0 +1,49 @@ +/* + * 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.UnsupportedEncodingException; + +import com.pff.exceptions.PSTException; + +/** + * An implementation of the LZFu algorithm to decompress RTF content + * @author Richard Johnson + */ +public class LZFu { + + + +} diff --git a/deprecated/PSTFile.java b/deprecated/PSTFile.java new file mode 100644 index 0000000..596e5f6 --- /dev/null +++ b/deprecated/PSTFile.java @@ -0,0 +1,938 @@ +/** + * 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.*; + +/** + * PSTFile is the containing class that allows you to access items within a .pst file. + * Start here, get the root of the folders and work your way down through your items. + * @author Richard Johnson + */ +public class PSTFile { + + public static final int ENCRYPTION_TYPE_NONE = 0; + public static final int ENCRYPTION_TYPE_COMPRESSIBLE = 1; + + private static final int MESSAGE_STORE_DESCRIPTOR_IDENTIFIER = 33; + private static final int ROOT_FOLDER_DESCRIPTOR_IDENTIFIER = 290; + + public static final int PST_TYPE_ANSI = 14; + protected static final int PST_TYPE_ANSI_2 = 15; + public static final int PST_TYPE_UNICODE = 23; + + // Known GUIDs + // Local IDs first + public static final int PS_PUBLIC_STRINGS = 0; + public static final int PSETID_Common = 1; + public static final int PSETID_Address = 2; + public static final int PS_INTERNET_HEADERS = 3; + public static final int PSETID_Appointment = 4; + public static final int PSETID_Meeting = 5; + public static final int PSETID_Log = 6; + public static final int PSETID_Messaging = 7; + public static final int PSETID_Note = 8; + public static final int PSETID_PostRss = 9; + public static final int PSETID_Task = 10; + public static final int PSETID_UnifiedMessaging = 11; + public static final int PS_MAPI = 12; + public static final int PSETID_AirSync = 13; + public static final int PSETID_Sharing = 14; + + // Now the string guids + private static final String guidStrings[] = + { "00020329-0000-0000-C000-000000000046", + "00062008-0000-0000-C000-000000000046", + "00062004-0000-0000-C000-000000000046", + "00020386-0000-0000-C000-000000000046", + "00062002-0000-0000-C000-000000000046", + "6ED8DA90-450B-101B-98DA-00AA003F1305", + "0006200A-0000-0000-C000-000000000046", + "41F28F13-83F4-4114-A584-EEDB5A6B0BFF", + "0006200E-0000-0000-C000-000000000046", + "00062041-0000-0000-C000-000000000046", + "00062003-0000-0000-C000-000000000046", + "4442858E-A9E3-4E80-B900-317A210CC15B", + "00020328-0000-0000-C000-000000000046", + "71035549-0739-4DCB-9163-00F0580DBBDF", + "00062040-0000-0000-C000-000000000046" }; + + private HashMap guidMap = new HashMap(); + + // the type of encryption the files uses. + private int encryptionType = 0; + + // our all important tree. + private LinkedHashMap> childrenDescriptorTree = null; + + private HashMap nameToId = new HashMap(); + private HashMap stringToId = new HashMap(); + private static HashMap idToName = new HashMap(); + private HashMap idToString = new HashMap(); + private byte[] guids = null; + + private int itemCount = 0; + + private RandomAccessFile in; + + /** + * constructor + * @param fileName + * @throws FileNotFoundException + * @throws PSTException + * @throws IOException + */ + public PSTFile(String fileName) + throws FileNotFoundException, PSTException, IOException + { + this(new File(fileName)); + } + public PSTFile(File fileName) + throws FileNotFoundException, PSTException, IOException + { + // attempt to open the file. + in = new RandomAccessFile(fileName, "r"); + + // get the first 4 bytes, should be !BDN + try { + byte[] temp = new byte[4]; + in.read(temp); + String strValue = new String(temp); + if (!strValue.equals("!BDN")) { + throw new PSTException("Invalid file header: "+strValue+", expected: !BDN"); + } + + // make sure we are using a supported version of a PST... + byte[] fileTypeBytes = new byte[2]; + //in.seek(10); + seek(10); + in.read(fileTypeBytes); + // ANSI file types can be 14 or 15: + if (fileTypeBytes[0] == PSTFile.PST_TYPE_ANSI_2) { + fileTypeBytes[0] = PSTFile.PST_TYPE_ANSI; + } + if (fileTypeBytes[0] != PSTFile.PST_TYPE_ANSI && + fileTypeBytes[0] != PSTFile.PST_TYPE_UNICODE) + { + throw new PSTException("Unrecognised PST File version: "+fileTypeBytes[0]); + } + this.pstFileType = fileTypeBytes[0]; + + // make sure encryption is turned off at this stage... + if (this.getPSTFileType() == PST_TYPE_ANSI) { + //in.seek(461); + seek(461); + } else { + //in.seek(513); + seek(513); + } + encryptionType = in.readByte(); + if (encryptionType == 0x02) { + throw new PSTException("Only unencrypted and compressable PST files are supported at this time"); + } + + // build out name to id map. + processNameToIdMap(in); + + } catch (IOException err) { + throw new PSTException("Unable to read PST Sig", err); + } + + } + + private int pstFileType = 0; + public int getPSTFileType() { + return pstFileType; + } + + /** + * read the name-to-id map from the file and load it in + * @param in + * @throws IOException + * @throws PSTException + */ + private void processNameToIdMap(RandomAccessFile in) + throws IOException, PSTException + { + + // Create our guid map + for ( int i = 0; i < guidStrings.length; ++i ) { + UUID uuid = UUID.fromString(guidStrings[i]); + guidMap.put(uuid, i); +/* + System.out.printf("guidMap[{%s}] = %d\n", uuid.toString(), i); +/**/ + } + + // process the name to id map + DescriptorIndexNode nameToIdMapDescriptorNode = (getDescriptorIndexNode(97)); + //nameToIdMapDescriptorNode.readData(this); + + // get the descriptors if we have them + HashMap localDescriptorItems = null; + if (nameToIdMapDescriptorNode.localDescriptorsOffsetIndexIdentifier != 0) { + //PSTDescriptor descriptor = new PSTDescriptor(this, nameToIdMapDescriptorNode.localDescriptorsOffsetIndexIdentifier); + //localDescriptorItems = descriptor.getChildren(); + localDescriptorItems = this.getPSTDescriptorItems(nameToIdMapDescriptorNode.localDescriptorsOffsetIndexIdentifier); + } + + // process the map + //PSTTableBC bcTable = new PSTTableBC(nameToIdMapDescriptorNode.dataBlock.data, nameToIdMapDescriptorNode.dataBlock.blockOffsets); + OffsetIndexItem off = this.getOffsetIndexNode(nameToIdMapDescriptorNode.dataOffsetIndexIdentifier); + PSTNodeInputStream nodein = new PSTNodeInputStream(this, off); + byte[] tmp = new byte[1024]; + nodein.read(tmp); + PSTTableBC bcTable = new PSTTableBC(nodein); + + HashMap tableItems = (bcTable.getItems()); + // Get the guids + PSTTableBCItem guidEntry = tableItems.get(2); // PidTagNameidStreamGuid + guids = getData(guidEntry, localDescriptorItems); + int nGuids = guids.length / 16; + UUID[] uuidArray = new UUID[nGuids]; + int[] uuidIndexes = new int[nGuids]; + int offset = 0; + for ( int i = 0; i < nGuids; ++i ) { + long mostSigBits = (PSTObject.convertLittleEndianBytesToLong(guids, offset, offset+4) << 32) | + (PSTObject.convertLittleEndianBytesToLong(guids, offset+4, offset+6) << 16) | + PSTObject.convertLittleEndianBytesToLong(guids, offset+6, offset+8); + long leastSigBits = PSTObject.convertBigEndianBytesToLong(guids, offset+8, offset+16); + uuidArray[i] = new UUID(mostSigBits, leastSigBits); + if ( guidMap.containsKey(uuidArray[i]) ) { + uuidIndexes[i] = guidMap.get(uuidArray[i]); + } else { + uuidIndexes[i] = -1; // We don't know this guid + } +/* + System.out.printf("uuidArray[%d] = {%s},%d\n", i, uuidArray[i].toString(), uuidIndexes[i]); +/**/ + offset += 16; + } + + // if we have a reference to an internal descriptor + PSTTableBCItem mapEntries = tableItems.get(3); // + byte[] nameToIdByte = getData(mapEntries, localDescriptorItems); + + PSTTableBCItem stringMapEntries = tableItems.get(4); // + byte[] stringNameToIdByte = getData(stringMapEntries, localDescriptorItems); + + // process the entries + for (int x = 0; x+8 < nameToIdByte.length; x += 8) { + int dwPropertyId = (int)PSTObject.convertLittleEndianBytesToLong(nameToIdByte, x, x+4); + int wGuid = (int)PSTObject.convertLittleEndianBytesToLong(nameToIdByte, x+4, x+6); + int wPropIdx = ((int)PSTObject.convertLittleEndianBytesToLong(nameToIdByte, x+6, x+8)); + + if ( (wGuid & 0x0001) == 0 ) { + wPropIdx += 0x8000; + wGuid >>= 1; + int guidIndex; + if ( wGuid == 1 ) { + guidIndex = PS_MAPI; + } else if ( wGuid == 2 ) { + guidIndex = PS_PUBLIC_STRINGS; + } else { + guidIndex = uuidIndexes[wGuid-3]; + } + nameToId.put((long)dwPropertyId | ((long)guidIndex << 32), wPropIdx); + idToName.put(wPropIdx, (long)dwPropertyId); +/* + System.out.printf("0x%08X:%04X, 0x%08X\n", dwPropertyId, guidIndex, wPropIdx); +/**/ + } else { + // else the identifier is a string + // dwPropertyId becomes thHke byte offset into the String stream in which the string name of the property is stored. + int len = (int)PSTObject.convertLittleEndianBytesToLong( + stringNameToIdByte, + dwPropertyId, + dwPropertyId+4 + ); + byte[] keyByteValue = new byte[len]; + System.arraycopy(stringNameToIdByte, dwPropertyId+4, keyByteValue, 0, keyByteValue.length); + wPropIdx += 0x8000; + String key = new String(keyByteValue, "UTF-16LE"); + stringToId.put(key, wPropIdx); + idToString.put(wPropIdx, key); + /* + if (wPropIdx == 32784) { + System.out.println("here!" + dwPropertyId); + System.out.println(key); + //System.out.println(32784 - 0x8000); + } + */ + } + } + } + + + private byte [] getData(PSTTableItem item, HashMap localDescriptorItems) + throws IOException, PSTException + { + if ( item.data.length != 0 ) { + return item.data; + } + + if ( localDescriptorItems == null ) { + throw new PSTException("External reference but no localDescriptorItems in PSTFile.getData()"); + } + + if ( item.entryValueType != 0x0102 ) { + throw new PSTException("Attempting to get non-binary data in PSTFile.getData()"); + } + + PSTDescriptorItem mapDescriptorItem = localDescriptorItems.get(item.entryValueReference); + if (mapDescriptorItem == null) { + throw new PSTException ("not here "+item.entryValueReference + "\n"+localDescriptorItems.keySet()); + } + return mapDescriptorItem.getData(); + } + + int getNameToIdMapItem(int key, int propertySetIndex) + { + long lKey = ((long)propertySetIndex << 32) | (long)key; + Integer i = nameToId.get(lKey); + if ( i == null ) + { + return -1; + } + return i; + } + int getPublicStringToIdMapItem(String key) + { + Integer i = this.stringToId.get(key); + if (i == null) { + return -1; + } + return i; + } + + + static long getNameToIdMapKey(int id) + //throws PSTException + { + Long i = idToName.get(id); + if ( i == null ) + { + //throw new PSTException("Name to Id mapping not found"); + return -1; + } + return i; + } + + + + static private Properties propertyInternetCodePages = null; + static private boolean bCPFirstTime = true; + static String getInternetCodePageCharset(int propertyId) { + if ( bCPFirstTime ) { + bCPFirstTime = false; + propertyInternetCodePages = new Properties(); + try { + InputStream propertyStream = PSTFile.class.getResourceAsStream("/InternetCodepages.txt"); + if ( propertyStream != null ) { + propertyInternetCodePages.load(propertyStream); + } else { + propertyInternetCodePages = null; + } + } catch (FileNotFoundException e) { + propertyInternetCodePages = null; + e.printStackTrace(); + } catch (IOException e) { + propertyInternetCodePages = null; + e.printStackTrace(); + } + } + if ( propertyInternetCodePages != null ) { + return propertyInternetCodePages.getProperty(propertyId+""); + } + return null; + } + + + static private Properties propertyNames = null; + static private boolean bFirstTime = true; + + static String getPropertyName(int propertyId, boolean bNamed) { + if ( bFirstTime ) { + bFirstTime = false; + propertyNames = new Properties(); + try { + InputStream propertyStream = PSTFile.class.getResourceAsStream("/PropertyNames.txt"); + if ( propertyStream != null ) { + propertyNames.load(propertyStream); + } else { + propertyNames = null; + } + } catch (FileNotFoundException e) { + propertyNames = null; + e.printStackTrace(); + } catch (IOException e) { + propertyNames = null; + e.printStackTrace(); + } + } + + if ( propertyNames != null ) { + String key = String.format((bNamed ? "%08X" : "%04X"), propertyId); + return propertyNames.getProperty(key); + } + + return null; + } + + + static String getPropertyDescription(int entryType, int entryValueType) { + String ret = ""; + if ( entryType < 0x8000 ) { + String name = PSTFile.getPropertyName(entryType, false); + if ( name != null ) { + ret = String.format("%s:%04X: ", name, entryValueType); + } else { + ret = String.format("0x%04X:%04X: ", entryType, entryValueType); + } + } else { + long type = PSTFile.getNameToIdMapKey(entryType); + if ( type == -1 ) { + ret = String.format("0xFFFF(%04X):%04X: ", entryType, entryValueType); + } else { + String name = PSTFile.getPropertyName((int)type, true); + if ( name != null ) { + ret = String.format("%s(%04X):%04X: ", name, entryType, entryValueType); + } else { + ret = String.format("0x%04X(%04X):%04X: ", type, entryType, entryValueType); + } + } + } + + return ret; + } + + /** + * destructor just closes the file handle... + */ + @Override + protected void finalize() + throws IOException + { + in.close(); + } + + /** + * get the type of encryption the file uses + * @return encryption type used in the PST File + */ + public int getEncryptionType() { + return this.encryptionType; + } + + /** + * get the handle to the file we are currently accessing + */ + public RandomAccessFile getFileHandle() { + return this.in; + } + + + /** + * get the message store of the PST file. + * Note that this doesn't really have much information, better to look under the root folder + * @throws PSTException + * @throws IOException + */ + public PSTMessageStore getMessageStore() + throws PSTException, IOException + { + DescriptorIndexNode messageStoreDescriptor = getDescriptorIndexNode(MESSAGE_STORE_DESCRIPTOR_IDENTIFIER); + return new PSTMessageStore(this, messageStoreDescriptor); + } + + /** + * get the root folder for the PST file. + * You should find all of your data under here... + * @throws PSTException + * @throws IOException + */ + public PSTFolder getRootFolder() + throws PSTException, IOException + { + DescriptorIndexNode rootFolderDescriptor = getDescriptorIndexNode(ROOT_FOLDER_DESCRIPTOR_IDENTIFIER); + PSTFolder output = new PSTFolder(this, rootFolderDescriptor); + return output; + } + + + + PSTNodeInputStream readLeaf(long bid) + throws IOException, PSTException + { + //PSTFileBlock ret = null; + //PSTNodeInputStream ret = null; + + // get the index node for the descriptor index + OffsetIndexItem offsetItem = getOffsetIndexNode(bid); + return new PSTNodeInputStream(this, offsetItem); + + } + + + public int getLeafSize(long bid) + throws IOException, PSTException + { + OffsetIndexItem offsetItem = getOffsetIndexNode(bid); + + // Internal block? + if ( (offsetItem.indexIdentifier & 0x02) == 0 ) { + // No, return the raw size + return offsetItem.size; + } + + // we only need the first 8 bytes + byte[] data = new byte[8]; + //in.seek(offsetItem.fileOffset); + seek(offsetItem.fileOffset); + in.read(data); + + // we are an array, get the sum of the sizes... + return (int)PSTObject.convertLittleEndianBytesToLong(data, 4, 8); + } + + /** + * Read a file offset from the file + * PST Files have this tendency to store file offsets (pointers) in 8 little endian bytes. + * Convert this to a long for seeking to. + * @param in handle for PST file + * @param startOffset where to read the 8 bytes from + * @return long representing the read location + * @throws IOException + */ + protected long extractLEFileOffset(long startOffset) + throws IOException + { + long offset = 0; + if (this.getPSTFileType() == PSTFile.PST_TYPE_ANSI) { + //in.seek(startOffset); + seek(startOffset); + byte[] temp = new byte[4]; + in.read(temp); + offset |= temp[3] & 0xff; + offset <<= 8; + offset |= temp[2] & 0xff; + offset <<= 8; + offset |= temp[1] & 0xff; + offset <<= 8; + offset |= temp[0] & 0xff; + } else { + //in.seek(startOffset); + seek(startOffset); + byte[] temp = new byte[8]; + in.read(temp); + offset = temp[7] & 0xff; + long tmpLongValue; + for (int x = 6; x >= 0; x--) { + offset = offset << 8; + tmpLongValue = (long)temp[x] & 0xff; + offset |= tmpLongValue; + } + } + + return offset; + } + + /** + * Generic function used by getOffsetIndexNode and getDescriptorIndexNode for navigating the PST B-Trees + * @param in + * @param index + * @param descTree + * @return + * @throws IOException + * @throws PSTException + */ + private byte[] findBtreeItem(/*RandomAccessFile in, */long index, boolean descTree) + throws IOException, PSTException + { + + long btreeStartOffset; + // first find the starting point for the offset index + if (this.getPSTFileType() == PST_TYPE_ANSI) { + btreeStartOffset = this.extractLEFileOffset(196); + if (descTree) { + btreeStartOffset = this.extractLEFileOffset(188); + } + } else { + btreeStartOffset = this.extractLEFileOffset(240); + if (descTree) { + btreeStartOffset = this.extractLEFileOffset(224); + } + } + + // okay, what we want to do is navigate the tree until you reach the bottom.... + // try and read the index b-tree + byte[] temp = new byte[2]; + if (this.getPSTFileType() == PST_TYPE_ANSI) { + //in.seek(btreeStartOffset+500); + seek(btreeStartOffset+500); + } else { + //in.seek(btreeStartOffset+496); + seek(btreeStartOffset+496); + } + in.read(temp); + while ((temp[0] == 0xffffff80 && temp[1] == 0xffffff80 && !descTree) || + (temp[0] == 0xffffff81 && temp[1] == 0xffffff81 && descTree)) + { + + // get the rest of the data.... + byte[] branchNodeItems; + if (this.getPSTFileType() == PST_TYPE_ANSI) { + branchNodeItems = new byte[496]; + } else { + branchNodeItems = new byte[488]; + } + //in.seek(btreeStartOffset); + seek(btreeStartOffset); + in.read(branchNodeItems); + + int numberOfItems = in.read(); + in.read(); // maxNumberOfItems + in.read(); // itemSize + int levelsToLeaf = in.read(); + + if (levelsToLeaf > 0) { + boolean found = false; + for (int x = 0; x < numberOfItems; x++) { + if (this.getPSTFileType() == PST_TYPE_ANSI) { + long indexIdOfFirstChildNode = extractLEFileOffset(btreeStartOffset + (x * 12)); + if (indexIdOfFirstChildNode > index) { + // get the address for the child first node in this group + btreeStartOffset = extractLEFileOffset(btreeStartOffset+((x-1) * 12)+8); + //in.seek(btreeStartOffset+500); + seek(btreeStartOffset+500); + in.read(temp); + found = true; + break; + } + } else { + long indexIdOfFirstChildNode = extractLEFileOffset(btreeStartOffset + (x * 24)); + if (indexIdOfFirstChildNode > index) { + // get the address for the child first node in this group + btreeStartOffset = extractLEFileOffset(btreeStartOffset+((x-1) * 24)+16); + //in.seek(btreeStartOffset+496); + seek(btreeStartOffset+496); + in.read(temp); + found = true; + break; + } + } + } + if (!found) { + // it must be in the very last branch... + if (this.getPSTFileType() == PST_TYPE_ANSI) { + btreeStartOffset = extractLEFileOffset(btreeStartOffset+((numberOfItems-1) * 12)+8); + //in.seek(btreeStartOffset+500); + seek(btreeStartOffset+500); + in.read(temp); + } else { + btreeStartOffset = extractLEFileOffset(btreeStartOffset+((numberOfItems-1) * 24)+16); + //in.seek(btreeStartOffset+496); + seek(btreeStartOffset+496); + in.read(temp); + } + } + } + else + { + // we are at the bottom of the tree... + // we want to get our file offset! + for (int x = 0; x < numberOfItems; x++) { + + if (this.getPSTFileType() == PSTFile.PST_TYPE_ANSI) { + if (descTree) + { + // The 32-bit descriptor index b-tree leaf node item + //in.seek(btreeStartOffset + (x * 16)); + seek(btreeStartOffset + (x * 16)); + temp = new byte[4]; + in.read(temp); + if (PSTObject.convertLittleEndianBytesToLong(temp) == index) { + // give me the offset index please! + //in.seek(btreeStartOffset + (x * 16)); + seek(btreeStartOffset + (x * 16)); + temp = new byte[16]; + in.read(temp); + return temp; + } + } + else + { + // The 32-bit (file) offset index item + long indexIdOfFirstChildNode = extractLEFileOffset(btreeStartOffset + (x * 12)); + + if (indexIdOfFirstChildNode == index) { + // we found it!!!! OMG + //System.out.println("item found as item #"+x); + //in.seek(btreeStartOffset + (x * 12)); + seek(btreeStartOffset + (x * 12)); + + temp = new byte[12]; + in.read(temp); + return temp; + } + } + } else { + if (descTree) + { + // The 64-bit descriptor index b-tree leaf node item + //in.seek(btreeStartOffset + (x * 32)); + seek(btreeStartOffset + (x * 32)); + + temp = new byte[4]; + in.read(temp); + if (PSTObject.convertLittleEndianBytesToLong(temp) == index) { + // give me the offset index please! + //in.seek(btreeStartOffset + (x * 32)); + seek(btreeStartOffset + (x * 32)); + temp = new byte[32]; + in.read(temp); + return temp; + } + } + else + { + // The 64-bit (file) offset index item + long indexIdOfFirstChildNode = extractLEFileOffset(btreeStartOffset + (x * 24)); + + if (indexIdOfFirstChildNode == index) { + // we found it!!!! OMG + //System.out.println("item found as item #"+x); + //in.seek(btreeStartOffset + (x * 24)); + seek(btreeStartOffset + (x * 24)); + + temp = new byte[24]; + in.read(temp); + return temp; + } + } + } + } + throw new PSTException("Unable to find "+index); + } + } + + throw new PSTException("Unable to find node: "+index); + } + + /** + * navigate the internal descriptor B-Tree and find a specific item + * @param in + * @param identifier + * @return the descriptor node for the item + * @throws IOException + * @throws PSTException + */ + DescriptorIndexNode getDescriptorIndexNode(long identifier) + throws IOException, PSTException + { + return new DescriptorIndexNode(findBtreeItem(/*in,*/ identifier, true), this.getPSTFileType()); + } + + /** + * navigate the internal index B-Tree and find a specific item + * @param in + * @param identifier + * @return the offset index item + * @throws IOException + * @throws PSTException + */ + OffsetIndexItem getOffsetIndexNode(long identifier) + throws IOException, PSTException + { + return new OffsetIndexItem(findBtreeItem(/*in,*/ identifier, false), this.getPSTFileType()); + } + + + /** + * parse a PSTDescriptor and get all of its items + */ + HashMap getPSTDescriptorItems(long localDescriptorsOffsetIndexIdentifier) + throws PSTException, IOException + { + return this.getPSTDescriptorItems(this.readLeaf(localDescriptorsOffsetIndexIdentifier)); + } + HashMap getPSTDescriptorItems(PSTNodeInputStream in) + throws PSTException, IOException + { + // make sure the signature is correct + in.seek(0); + int sig = in.read(); + if (sig != 0x2) { + throw new PSTException("Unable to process descriptor node, bad signature: "+sig); + } + + HashMap output = new HashMap(); + int numberOfItems = (int)in.seekAndReadLong(2, 2); + int offset; + if (this.getPSTFileType() == PSTFile.PST_TYPE_ANSI) { + offset = 4; + } else { + offset = 8; + } + + byte[] data = new byte[(int)in.length()]; + in.seek(0); + in.read(data); + + for (int x = 0; x < numberOfItems; x++) { + PSTDescriptorItem item = new PSTDescriptorItem(data, offset, this); + output.put(item.descriptorIdentifier, item); + if (this.getPSTFileType() == PSTFile.PST_TYPE_ANSI) { + offset += 12; + } else { + offset += 24; + } + } + + return output; + } + + /** + * Build the children descriptor tree + * This goes through the entire descriptor B-Tree and adds every item to the childrenDescriptorTree. + * This is used as fallback when the nodes that list file contents are broken. + * @param in + * @throws IOException + * @throws PSTException + */ + LinkedHashMap> getChildDescriptorTree() + throws IOException, PSTException + { + if (this.childrenDescriptorTree == null) { + long btreeStartOffset = 0; + if (this.getPSTFileType() == PST_TYPE_ANSI) { + btreeStartOffset = this.extractLEFileOffset(188); + } else { + btreeStartOffset = this.extractLEFileOffset(224); + } + this.childrenDescriptorTree = new LinkedHashMap>(); + processDescriptorBTree(btreeStartOffset); + } + return this.childrenDescriptorTree; + } + + /** + * Recursive function for building the descriptor tree, used by buildDescriptorTree + * @param in + * @param btreeStartOffset + * @throws IOException + * @throws PSTException + */ + private void processDescriptorBTree(long btreeStartOffset) + throws IOException, PSTException + { + byte[] temp = new byte[2]; + if (this.getPSTFileType() == PST_TYPE_ANSI) { + in.seek(btreeStartOffset+500); + } else { + in.seek(btreeStartOffset+496); + } + in.read(temp); + + if ((temp[0] == 0xffffff81 && temp[1] == 0xffffff81)) { + + if (this.getPSTFileType() == PST_TYPE_ANSI) { + in.seek(btreeStartOffset+496); + } else { + in.seek(btreeStartOffset+488); + } + + int numberOfItems = in.read(); + in.read(); // maxNumberOfItems + in.read(); // itemSize + int levelsToLeaf = in.read(); + + if (levelsToLeaf > 0) { + for (int x = 0; x < numberOfItems; x++) { + if (this.getPSTFileType() == PST_TYPE_ANSI) { + long branchNodeItemStartIndex = (btreeStartOffset + (12*x)); + long nextLevelStartsAt = this.extractLEFileOffset(branchNodeItemStartIndex+8); + processDescriptorBTree(nextLevelStartsAt); + } else { + long branchNodeItemStartIndex = (btreeStartOffset + (24*x)); + long nextLevelStartsAt = this.extractLEFileOffset(branchNodeItemStartIndex+16); + processDescriptorBTree(nextLevelStartsAt); + } + } + } else { + for (int x = 0; x < numberOfItems; x++) { + // The 64-bit descriptor index b-tree leaf node item + // give me the offset index please! + if (this.getPSTFileType() == PSTFile.PST_TYPE_ANSI) { + in.seek(btreeStartOffset + (x * 16)); + temp = new byte[16]; + in.read(temp); + } else { + in.seek(btreeStartOffset + (x * 32)); + temp = new byte[32]; + in.read(temp); + } + + DescriptorIndexNode tempNode = new DescriptorIndexNode(temp, this.getPSTFileType()); + + // we don't want to be children of ourselves... + if (tempNode.parentDescriptorIndexIdentifier == tempNode.descriptorIdentifier) { + // skip! + } else if (childrenDescriptorTree.containsKey(tempNode.parentDescriptorIndexIdentifier)) { + // add this entry to the existing list of children + LinkedList children = childrenDescriptorTree.get(tempNode.parentDescriptorIndexIdentifier); + children.add(tempNode); + } else { + // create a new entry and add this one to that + LinkedList children = new LinkedList(); + children.add(tempNode); + childrenDescriptorTree.put(tempNode.parentDescriptorIndexIdentifier, children); + } + this.itemCount++; + } + } + } else { + PSTObject.printHexFormatted(temp, true); + throw new PSTException("Unable to read descriptor node, is not a descriptor"); + } + } + + + + + private void seek(long pos) throws IOException { + System.out.println("seeking file => "+pos); + this.in.seek(pos); + } +} diff --git a/example/TestRandomAccess.java b/example/TestRandomAccess.java new file mode 100644 index 0000000..d0fdc97 --- /dev/null +++ b/example/TestRandomAccess.java @@ -0,0 +1,15 @@ +package example; + +public class TestRandomAccess { + + public TestRandomAccess() { + + } + + + public static void main(String[] args) { + + + } + +} From 95ba3923bad746e77154a8d0428d2b623e3fcde0 Mon Sep 17 00:00:00 2001 From: lepicarn Date: Fri, 17 Jul 2015 10:31:44 +0200 Subject: [PATCH 3/3] update with last commits from base fork (17/07/2015) --- .classpath | 3 ++- PropertyNames.txt | 3 +++ com/pff/objects/PSTAppointment.java | 10 ++++++++- com/pff/objects/PSTMessage.java | 21 +++++++++++++++++-- com/pff/objects/PSTObject.java | 6 +++++- .../objects/sub/PSTAppointmentRecurrence.java | 8 +++++++ 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/.classpath b/.classpath index 6bdca60..a0cbe33 100644 --- a/.classpath +++ b/.classpath @@ -1,6 +1,7 @@ - + + diff --git a/PropertyNames.txt b/PropertyNames.txt index 572a87f..16e6278 100644 --- a/PropertyNames.txt +++ b/PropertyNames.txt @@ -135,6 +135,7 @@ 3FDE=PidTagInternetCodepage 3FF1=PidTagMessageLocaleId 3FFD=PidTagMessageCodepage +3ff9=PidTagCreatorName 4019=PidTagSenderFlags 401A=PidTagSentRepresentingFlags 401B=PidTagReceivedByFlags @@ -170,6 +171,8 @@ 7FFD=PidTagAttachmentFlags 7FFE=PidTagAttachmentHidden 7FFF=PidTagAttachmentContactPhoto +3FFA=PidTagLastModifiedName_W +3FFB=PidTagLastModifierEntryId 00000001=PidLidAttendeeCriticalChange 00000002=PidLidWhere 00000003=PidLidGlobalObjectId diff --git a/com/pff/objects/PSTAppointment.java b/com/pff/objects/PSTAppointment.java index c1e9f0c..df69cce 100644 --- a/com/pff/objects/PSTAppointment.java +++ b/com/pff/objects/PSTAppointment.java @@ -38,6 +38,7 @@ import java.util.HashMap; import com.pff.exceptions.PSTException; +import com.pff.objects.sub.PSTGlobalObjectId; import com.pff.objects.sub.PSTTimeZone; import com.pff.parsing.DescriptorIndexNode; import com.pff.parsing.PSTDescriptorItem; @@ -194,7 +195,14 @@ public int getLocaleId() { return getIntItem(0x3ff1); } - public byte[] getGlobalObjectId() { + /*public byte[] getGlobalObjectId() { return getBinaryItem(pstFile.getNameToIdMapItem(0x00000003, PSTSource.PSETID_Meeting)); + }*/ + public PSTGlobalObjectId getGlobalObjectId() { + return new PSTGlobalObjectId(getBinaryItem(pstFile.getNameToIdMapItem(0x00000003, PSTSource.PSETID_Meeting))); + } + + public PSTGlobalObjectId getCleanGlobalObjectId() { + return new PSTGlobalObjectId(getBinaryItem(pstFile.getNameToIdMapItem(0x00000023, PSTSource.PSETID_Meeting))); } } diff --git a/com/pff/objects/PSTMessage.java b/com/pff/objects/PSTMessage.java index 0ad6f9b..4cf58e9 100644 --- a/com/pff/objects/PSTMessage.java +++ b/com/pff/objects/PSTMessage.java @@ -39,6 +39,7 @@ import com.pff.PSTUtils; import com.pff.exceptions.PSTException; +import com.pff.objects.sub.PSTConversationIndex; import com.pff.objects.sub.PSTRecipient; import com.pff.parsing.DescriptorIndexNode; import com.pff.parsing.PSTDescriptorItem; @@ -329,8 +330,8 @@ public boolean getMessageCcMe () { /** * Message addressed to me ASCII or Unicode string */ - public String getMessageRecipMe () { - return this.getStringItem(0x0059); + public boolean getMessageRecipMe () { + return this.getIntItem(0x0059) != 0; } /** * Response requested Boolean @@ -597,6 +598,10 @@ public Date getMessageDeliveryTime() { // return (this.getIntItem(0x0e17) & 0x2000) != 0; // } + public int getNativeBodyType() { + return this.getIntItem(0x1016); + } + /** * Message content properties */ @@ -1026,4 +1031,16 @@ public String toString() { this.localDescriptorItems; } + + public byte[] getConversationId() { + return getBinaryItem(0x3013); + } + + public PSTConversationIndex getConversationIndex() { + return new PSTConversationIndex(getBinaryItem(0x0071)); + } + + public boolean isConversationIndexTracking() { + return getBooleanItem(0x3016, false); + } } diff --git a/com/pff/objects/PSTObject.java b/com/pff/objects/PSTObject.java index 4371e62..aab6c34 100644 --- a/com/pff/objects/PSTObject.java +++ b/com/pff/objects/PSTObject.java @@ -139,7 +139,11 @@ public DescriptorIndexNode getDescriptorNode() { * @return item's descriptor node identifier */ public long getDescriptorNodeId() { - return this.descriptorIndexNode.descriptorIdentifier; + //return this.descriptorIndexNode.descriptorIdentifier; + if (this.descriptorIndexNode != null) { // Prevent null pointer exceptions for embedded messages + return this.descriptorIndexNode.descriptorIdentifier; + } + return 0; } public int getNodeType() { diff --git a/com/pff/objects/sub/PSTAppointmentRecurrence.java b/com/pff/objects/sub/PSTAppointmentRecurrence.java index 533e173..f78f138 100644 --- a/com/pff/objects/sub/PSTAppointmentRecurrence.java +++ b/com/pff/objects/sub/PSTAppointmentRecurrence.java @@ -58,6 +58,14 @@ public class PSTAppointmentRecurrence { // Access methods + public Calendar[] getDeletedInstanceDates() { + return DeletedInstanceDates; + } + + public Calendar[] getModifiedInstanceDates() { + return ModifiedInstanceDates; + } + public short getExceptionCount() { return ExceptionCount; }