├── .gitignore ├── LICENSE.APACHE-2.0.txt ├── LICENSE.LGPL.txt ├── README.md ├── README.txt ├── pom.xml └── src ├── main ├── java │ ├── com │ │ └── pff │ │ │ ├── DescriptorIndexNode.java │ │ │ ├── LZFu.java │ │ │ ├── OffsetIndexItem.java │ │ │ ├── PSTActivity.java │ │ │ ├── PSTAppointment.java │ │ │ ├── PSTAppointmentException.java │ │ │ ├── PSTAppointmentRecurrence.java │ │ │ ├── PSTAttachment.java │ │ │ ├── PSTByteFileContent.java │ │ │ ├── PSTContact.java │ │ │ ├── PSTConversationIndex.java │ │ │ ├── PSTDescriptorItem.java │ │ │ ├── PSTDistList.java │ │ │ ├── PSTException.java │ │ │ ├── PSTFile.java │ │ │ ├── PSTFileContent.java │ │ │ ├── PSTFolder.java │ │ │ ├── PSTGlobalObjectId.java │ │ │ ├── PSTMessage.java │ │ │ ├── PSTMessageStore.java │ │ │ ├── PSTNodeInputStream.java │ │ │ ├── PSTObject.java │ │ │ ├── PSTRAFileContent.java │ │ │ ├── PSTRecipient.java │ │ │ ├── PSTRss.java │ │ │ ├── PSTTable.java │ │ │ ├── PSTTable7C.java │ │ │ ├── PSTTable7CItem.java │ │ │ ├── PSTTableBC.java │ │ │ ├── PSTTableBCItem.java │ │ │ ├── PSTTableItem.java │ │ │ ├── PSTTask.java │ │ │ └── PSTTimeZone.java │ └── example │ │ ├── Test.java │ │ ├── TestGui.java │ │ └── TestGui.rs └── resources │ ├── InternetCodepages.txt │ ├── PIDLongID.csv │ ├── PIDName.csv │ ├── PIDShortID.csv │ └── PropertyNames.txt └── test ├── java └── com │ └── pff │ ├── AppointmentTest.java │ ├── DistListTest.java │ ├── PSTGlobalObjectIdTest.java │ ├── PasswordTest.java │ └── Version36Test.java └── resources ├── dist-list.pst ├── example-2013.ost └── passworded.pst /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ -------------------------------------------------------------------------------- /LICENSE.APACHE-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /LICENSE.LGPL.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The PST File format is used by Outlook for the storage of emails. Over the years many people have accumulated a large amount of important email and other information into these files, this project aims to allow people to access and extract this information so that it may be migrated to other messaging systems. 2 | 3 | This project was originally based off the documentation created through the fantastic reverse engineering effort made by the [libpff](https://sourceforge.net/projects/libpff) project. The library has been improved with information provided by the release of the official PST specs by Microsoft. 4 | 5 | The functional goals are: 6 | 7 | * Efficiency; should be able to work with very large PST files with reasonable speed 8 | * Support for compressible encryption (on by default with newer versions of Outlook) 9 | * Intuitive API 10 | * Support for ANSI (32bit), Unicode (64bit) Outlook PST and Exchange OST Files. 11 | 12 | Things that the library will most likely not do: 13 | 14 | * Fix or work with broken PST files 15 | * Provide write access to PST files 16 | * Recover deleted email items 17 | 18 | For example usage of the library please see the TestGui application stored in the examples folder. Javadocs are available here: http://rjohnsondev.github.io/java-libpst 19 | 20 | Accessing the contents of a PSTFile is a matter of following the folder structure down to the desired email. This example reads a PST and prints the tree structure to the console: 21 | 22 | ```java 23 | package example; 24 | import com.pff.*; 25 | import java.util.*; 26 | 27 | public class Test { 28 | public static void main(String[] args) 29 | { 30 | new Test(args[0]); 31 | } 32 | 33 | public Test(String filename) { 34 | try { 35 | PSTFile pstFile = new PSTFile(filename); 36 | System.out.println(pstFile.getMessageStore().getDisplayName()); 37 | processFolder(pstFile.getRootFolder()); 38 | } catch (Exception err) { 39 | err.printStackTrace(); 40 | } 41 | } 42 | 43 | int depth = -1; 44 | public void processFolder(PSTFolder folder) 45 | throws PSTException, java.io.IOException 46 | { 47 | depth++; 48 | // the root folder doesn't have a display name 49 | if (depth > 0) { 50 | printDepth(); 51 | System.out.println(folder.getDisplayName()); 52 | } 53 | 54 | // go through the folders... 55 | if (folder.hasSubfolders()) { 56 | Vector childFolders = folder.getSubFolders(); 57 | for (PSTFolder childFolder : childFolders) { 58 | processFolder(childFolder); 59 | } 60 | } 61 | 62 | // and now the emails for this folder 63 | if (folder.getContentCount() > 0) { 64 | depth++; 65 | PSTMessage email = (PSTMessage)folder.getNextChild(); 66 | while (email != null) { 67 | printDepth(); 68 | System.out.println("Email: "+email.getSubject()); 69 | email = (PSTMessage)folder.getNextChild(); 70 | } 71 | depth--; 72 | } 73 | depth--; 74 | } 75 | 76 | public void printDepth() { 77 | for (int x = 0; x < depth-1; x++) { 78 | System.out.print(" | "); 79 | } 80 | System.out.print(" |- "); 81 | } 82 | } 83 | ``` 84 | 85 | Attachments can be read through PSTAttachment.getFileInputStream like so: 86 | 87 | ```java 88 | int numberOfAttachments = email.getNumberOfAttachments(); 89 | for (int x = 0; x < numberOfAttachments; x++) { 90 | PSTAttachment attach = email.getAttachment(x); 91 | InputStream attachmentStream = attach.getFileInputStream(); 92 | // both long and short filenames can be used for attachments 93 | String filename = attach.getLongFilename(); 94 | if (filename.isEmpty()) { 95 | filename = attach.getFilename(); 96 | } 97 | FileOutputStream out = new FileOutputStream(filename); 98 | // 8176 is the block size used internally and should give the best performance 99 | int bufferSize = 8176; 100 | byte[] buffer = new byte[bufferSize]; 101 | int count = attachmentStream.read(buffer); 102 | while (count == bufferSize) { 103 | out.write(buffer); 104 | count = attachmentStream.read(buffer); 105 | } 106 | byte[] endBuffer = new byte[count]; 107 | System.arraycopy(buffer, 0, endBuffer, 0, count); 108 | out.write(endBuffer); 109 | out.close(); 110 | attachmentStream.close(); 111 | } 112 | ``` 113 | 114 | Each object in the PST has a unique identifier called a descriptor node id. This can be useful for retrieving known objects quickly from the PST: 115 | 116 | ```java 117 | long id = email.getDescriptorNodeId(); 118 | pstObject = PSTObject.detectAndLoadPSTObject(pstFile, id); 119 | ``` 120 | 121 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | java-libpst is a pure java library for the reading of Outlook PST and OST files. 2 | 3 | For usage example, please see the javadocs and the example program located in the example package. 4 | 5 | java-libpst is licensed under both the LGPL and Apache License v2.0 -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | org.sonatype.oss 6 | oss-parent 7 | 7 8 | 9 | 10 | com.pff 11 | java-libpst 12 | jar 13 | 14 | java-libpst 15 | 0.9.5-SNAPSHOT 16 | A library to read PST files with java, without need for external libraries. 17 | https://code.google.com/p/java-libpst/ 18 | 19 | 20 | 21 | The Apache Software License, Version 2.0 22 | http://www.apache.org/licenses/LICENSE-2.0.txt 23 | repo 24 | 25 | 26 | 27 | 28 | https://github.com/rjohnsondev/java-libpst/tags/java-libpst-0.9.4 29 | scm:https://github.com/rjohnsondev/java-libpst.git/tags/java-libpst-0.9.4 30 | 31 | 32 | 33 | 34 | Richard Johnson 35 | @rjohnsondev 36 | 37 | 38 | 39 | 40 | 41 | UTF-8 42 | 43 | 44 | 45 | 46 | junit 47 | junit 48 | 4.13.1 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-compiler-plugin 58 | 3.5.1 59 | 60 | 1.8 61 | 1.8 62 | 63 | 64 | 65 | org.apache.maven.plugins 66 | maven-source-plugin 67 | 68 | 69 | attach-sources 70 | 71 | jar 72 | 73 | 74 | 75 | 76 | 77 | org.apache.maven.plugins 78 | maven-javadoc-plugin 79 | 2.9.1 80 | 81 | 82 | 83 | attach-javadocs 84 | 85 | jar 86 | 87 | 91 | 92 | 93 | 94 | 95 | org.apache.maven.plugins 96 | maven-gpg-plugin 97 | 98 | 99 | sign-artifacts 100 | verify 101 | 102 | sign 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | org.apache.maven.plugins 113 | maven-release-plugin 114 | 2.1 115 | 116 | forked-path 117 | false 118 | ${arguments} -Psonatype-oss-release 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/main/java/com/pff/DescriptorIndexNode.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | import java.io.IOException; 37 | 38 | /** 39 | * DescriptorIndexNode is a leaf item from the Descriptor index b-tree 40 | * It is like a pointer to an element in the PST file, everything has one... 41 | * 42 | * @author Richard Johnson 43 | */ 44 | public class DescriptorIndexNode { 45 | public int descriptorIdentifier; 46 | public long dataOffsetIndexIdentifier; 47 | public long localDescriptorsOffsetIndexIdentifier; 48 | public int parentDescriptorIndexIdentifier; 49 | public int itemType; 50 | 51 | // PSTFile.PSTFileBlock dataBlock = null; 52 | 53 | /** 54 | * parse the data out into something meaningful 55 | * 56 | * @param data the data 57 | * @param pstFileType the pst file type 58 | */ 59 | DescriptorIndexNode(final byte[] data, final int pstFileType) { 60 | // parse it out 61 | // first 4 bytes 62 | if (pstFileType == PSTFile.PST_TYPE_ANSI) { 63 | this.descriptorIdentifier = (int) PSTObject.convertLittleEndianBytesToLong(data, 0, 4); 64 | this.dataOffsetIndexIdentifier = (int) PSTObject.convertLittleEndianBytesToLong(data, 4, 8); 65 | this.localDescriptorsOffsetIndexIdentifier = (int) PSTObject.convertLittleEndianBytesToLong(data, 8, 12); 66 | this.parentDescriptorIndexIdentifier = (int) PSTObject.convertLittleEndianBytesToLong(data, 12, 16); 67 | // itemType = (int)PSTObject.convertLittleEndianBytesToLong(data, 68 | // 28, 32); 69 | } else { 70 | this.descriptorIdentifier = (int) PSTObject.convertLittleEndianBytesToLong(data, 0, 4); 71 | this.dataOffsetIndexIdentifier = (int) PSTObject.convertLittleEndianBytesToLong(data, 8, 16); 72 | this.localDescriptorsOffsetIndexIdentifier = (int) PSTObject.convertLittleEndianBytesToLong(data, 16, 24); 73 | this.parentDescriptorIndexIdentifier = (int) PSTObject.convertLittleEndianBytesToLong(data, 24, 28); 74 | this.itemType = (int) PSTObject.convertLittleEndianBytesToLong(data, 28, 32); 75 | } 76 | } 77 | 78 | /* 79 | * void readData(PSTFile file) 80 | * throws IOException, PSTException 81 | * { 82 | * if ( dataBlock == null ) { 83 | * dataBlock = file.readLeaf(dataOffsetIndexIdentifier); 84 | * } 85 | * } 86 | * 87 | */ 88 | 89 | PSTNodeInputStream getNodeInputStream(final PSTFile pstFile) throws IOException, PSTException { 90 | return new PSTNodeInputStream(pstFile, pstFile.getOffsetIndexNode(this.dataOffsetIndexIdentifier)); 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | 96 | return "DescriptorIndexNode\n" + "Descriptor Identifier: " + this.descriptorIdentifier + " (0x" 97 | + Long.toHexString(this.descriptorIdentifier) + ")\n" + "Data offset identifier: " 98 | + this.dataOffsetIndexIdentifier + " (0x" + Long.toHexString(this.dataOffsetIndexIdentifier) + ")\n" 99 | + "Local descriptors offset index identifier: " + this.localDescriptorsOffsetIndexIdentifier + " (0x" 100 | + Long.toHexString(this.localDescriptorsOffsetIndexIdentifier) + ")\n" 101 | + "Parent Descriptor Index Identifier: " + this.parentDescriptorIndexIdentifier + " (0x" 102 | + Long.toHexString(this.parentDescriptorIndexIdentifier) + ")\n" + "Item Type: " + this.itemType + " (0x" 103 | + Long.toHexString(this.itemType) + ")"; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/pff/LZFu.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | 35 | package com.pff; 36 | 37 | import java.io.UnsupportedEncodingException; 38 | 39 | /** 40 | * An implementation of the LZFu algorithm to decompress RTF content 41 | * 42 | * @author Richard Johnson 43 | */ 44 | public class LZFu { 45 | 46 | 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"; 47 | 48 | public static String decode(final byte[] data) throws PSTException { 49 | 50 | @SuppressWarnings("unused") 51 | final int compressedSize = (int) PSTObject.convertLittleEndianBytesToLong(data, 0, 4); 52 | final int uncompressedSize = (int) PSTObject.convertLittleEndianBytesToLong(data, 4, 8); 53 | final int compressionSig = (int) PSTObject.convertLittleEndianBytesToLong(data, 8, 12); 54 | @SuppressWarnings("unused") 55 | final int compressedCRC = (int) PSTObject.convertLittleEndianBytesToLong(data, 12, 16); 56 | 57 | if (compressionSig == 0x75465a4c) { 58 | // we are compressed... 59 | final byte[] output = new byte[uncompressedSize]; 60 | int outputPosition = 0; 61 | final byte[] lzBuffer = new byte[4096]; 62 | // preload our buffer. 63 | try { 64 | final byte[] bytes = LZFU_HEADER.getBytes("US-ASCII"); 65 | System.arraycopy(bytes, 0, lzBuffer, 0, LZFU_HEADER.length()); 66 | } catch (final UnsupportedEncodingException e) { 67 | e.printStackTrace(); 68 | } 69 | int bufferPosition = LZFU_HEADER.length(); 70 | int currentDataPosition = 16; 71 | 72 | // next byte is the flags, 73 | while (currentDataPosition < data.length - 2 && outputPosition < output.length) { 74 | int flags = data[currentDataPosition++] & 0xFF; 75 | for (int x = 0; x < 8 && outputPosition < output.length; x++) { 76 | final boolean isRef = ((flags & 1) == 1); 77 | flags >>= 1; 78 | if (isRef) { 79 | // get the starting point for the buffer and the 80 | // length to read 81 | final int refOffsetOrig = data[currentDataPosition++] & 0xFF; 82 | final int refSizeOrig = data[currentDataPosition++] & 0xFF; 83 | final int refOffset = (refOffsetOrig << 4) | (refSizeOrig >>> 4); 84 | final int refSize = (refSizeOrig & 0xF) + 2; 85 | // refOffset &= 0xFFF; 86 | try { 87 | // copy the data from the buffer 88 | int index = refOffset; 89 | for (int y = 0; y < refSize && outputPosition < output.length; y++) { 90 | output[outputPosition++] = lzBuffer[index]; 91 | lzBuffer[bufferPosition] = lzBuffer[index]; 92 | bufferPosition++; 93 | bufferPosition %= 4096; 94 | ++index; 95 | index %= 4096; 96 | } 97 | } catch (final Exception e) { 98 | e.printStackTrace(); 99 | } 100 | 101 | } else { 102 | // copy the byte over 103 | lzBuffer[bufferPosition] = data[currentDataPosition]; 104 | bufferPosition++; 105 | bufferPosition %= 4096; 106 | output[outputPosition++] = data[currentDataPosition++]; 107 | } 108 | } 109 | } 110 | 111 | if (outputPosition != uncompressedSize) { 112 | throw new PSTException(String.format("Error decompressing RTF! Expected %d bytes, got %d bytes\n", 113 | uncompressedSize, outputPosition)); 114 | } 115 | return new String(output).trim(); 116 | 117 | } else if (compressionSig == 0x414c454d) { 118 | // we are not compressed! 119 | // just return the rest of the contents as a string 120 | final byte[] output = new byte[data.length - 16]; 121 | System.arraycopy(data, 16, output, 0, data.length - 16); 122 | return new String(output).trim(); 123 | } 124 | 125 | return ""; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/pff/OffsetIndexItem.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | /** 37 | * OffsetIndexItem is a leaf item from the Offset index b-tree 38 | * Only really used internally to get the file offset for items 39 | * 40 | * @author Richard Johnson 41 | */ 42 | class OffsetIndexItem { 43 | long indexIdentifier; 44 | long fileOffset; 45 | int size; 46 | long cRef; 47 | 48 | OffsetIndexItem(final byte[] data, final int pstFileType) { 49 | if (pstFileType == PSTFile.PST_TYPE_ANSI) { 50 | this.indexIdentifier = PSTObject.convertLittleEndianBytesToLong(data, 0, 4); 51 | this.fileOffset = PSTObject.convertLittleEndianBytesToLong(data, 4, 8); 52 | this.size = (int) PSTObject.convertLittleEndianBytesToLong(data, 8, 10); 53 | this.cRef = (int) PSTObject.convertLittleEndianBytesToLong(data, 10, 12); 54 | } else { 55 | this.indexIdentifier = PSTObject.convertLittleEndianBytesToLong(data, 0, 8); 56 | this.fileOffset = PSTObject.convertLittleEndianBytesToLong(data, 8, 16); 57 | this.size = (int) PSTObject.convertLittleEndianBytesToLong(data, 16, 18); 58 | this.cRef = (int) PSTObject.convertLittleEndianBytesToLong(data, 16, 18); 59 | } 60 | // System.out.println("Data size: "+data.length); 61 | 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "OffsetIndexItem\n" + "Index Identifier: " + this.indexIdentifier + " (0x" 67 | + Long.toHexString(this.indexIdentifier) + ")\n" + "File Offset: " + this.fileOffset + " (0x" 68 | + Long.toHexString(this.fileOffset) + ")\n" + "cRef: " + this.cRef + " (0x" + Long.toHexString(this.cRef) 69 | + " bin:" + Long.toBinaryString(this.cRef) + ")\n" + "Size: " + this.size + " (0x" 70 | + Long.toHexString(this.size) + ")"; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | import java.io.IOException; 37 | import java.util.Date; 38 | import java.util.HashMap; 39 | 40 | /** 41 | * PSTActivity represents Journal entries 42 | * 43 | * @author Richard Johnson 44 | */ 45 | public class PSTActivity extends PSTMessage { 46 | 47 | /** 48 | * Instantiates a new Pst activity. 49 | * 50 | * @param theFile the the file 51 | * @param descriptorIndexNode the descriptor index node 52 | * @throws PSTException the pst exception 53 | * @throws IOException the io exception 54 | */ 55 | public PSTActivity(final PSTFile theFile, final DescriptorIndexNode descriptorIndexNode) 56 | throws PSTException, IOException { 57 | super(theFile, descriptorIndexNode); 58 | } 59 | 60 | /** 61 | * Instantiates a new Pst activity. 62 | * 63 | * @param theFile the the file 64 | * @param folderIndexNode the folder index node 65 | * @param table the table 66 | * @param localDescriptorItems the local descriptor items 67 | */ 68 | public PSTActivity(final PSTFile theFile, final DescriptorIndexNode folderIndexNode, final PSTTableBC table, 69 | final HashMap localDescriptorItems) { 70 | super(theFile, folderIndexNode, table, localDescriptorItems); 71 | } 72 | 73 | /** 74 | * Type 75 | * 76 | * @return the log type 77 | */ 78 | public String getLogType() { 79 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008700, PSTFile.PSETID_Log)); 80 | } 81 | 82 | /** 83 | * Start 84 | * 85 | * @return the log start 86 | */ 87 | public Date getLogStart() { 88 | return this.getDateItem(this.pstFile.getNameToIdMapItem(0x00008706, PSTFile.PSETID_Log)); 89 | } 90 | 91 | /** 92 | * Duration 93 | * 94 | * @return the log duration 95 | */ 96 | public int getLogDuration() { 97 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008707, PSTFile.PSETID_Log)); 98 | } 99 | 100 | /** 101 | * End 102 | * 103 | * @return the log end 104 | */ 105 | public Date getLogEnd() { 106 | return this.getDateItem(this.pstFile.getNameToIdMapItem(0x00008708, PSTFile.PSETID_Log)); 107 | } 108 | 109 | /** 110 | * LogFlags 111 | * 112 | * @return the log flags 113 | */ 114 | public int getLogFlags() { 115 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x0000870c, PSTFile.PSETID_Log)); 116 | } 117 | 118 | /** 119 | * DocPrinted 120 | * 121 | * @return the boolean 122 | */ 123 | public boolean isDocumentPrinted() { 124 | return (this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x0000870e, PSTFile.PSETID_Log))); 125 | } 126 | 127 | /** 128 | * DocSaved 129 | * 130 | * @return the boolean 131 | */ 132 | public boolean isDocumentSaved() { 133 | return (this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x0000870f, PSTFile.PSETID_Log))); 134 | } 135 | 136 | /** 137 | * DocRouted 138 | * 139 | * @return the boolean 140 | */ 141 | public boolean isDocumentRouted() { 142 | return (this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x00008710, PSTFile.PSETID_Log))); 143 | } 144 | 145 | /** 146 | * DocPosted 147 | * 148 | * @return the boolean 149 | */ 150 | public boolean isDocumentPosted() { 151 | return (this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x00008711, PSTFile.PSETID_Log))); 152 | } 153 | 154 | /** 155 | * Type Description 156 | * 157 | * @return the log type desc 158 | */ 159 | public String getLogTypeDesc() { 160 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008712, PSTFile.PSETID_Log)); 161 | } 162 | 163 | @Override 164 | public String toString() { 165 | return "Type ASCII or Unicode string: " + this.getLogType() + "\n" + "Start Filetime: " + this.getLogStart() 166 | + "\n" + "Duration Integer 32-bit signed: " + this.getLogDuration() + "\n" + "End Filetime: " 167 | + this.getLogEnd() + "\n" + "LogFlags Integer 32-bit signed: " + this.getLogFlags() + "\n" 168 | + "DocPrinted Boolean: " + this.isDocumentPrinted() + "\n" + "DocSaved Boolean: " + this.isDocumentSaved() 169 | + "\n" + "DocRouted Boolean: " + this.isDocumentRouted() + "\n" + "DocPosted Boolean: " 170 | + this.isDocumentPosted() + "\n" + "TypeDescription ASCII or Unicode string: " + this.getLogTypeDesc(); 171 | 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTAppointment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | import java.io.IOException; 37 | import java.util.Date; 38 | import java.util.HashMap; 39 | 40 | /** 41 | * PSTAppointment is for Calendar items 42 | * 43 | * @author Richard Johnson 44 | */ 45 | public class PSTAppointment extends PSTMessage { 46 | 47 | PSTAppointment(final PSTFile theFile, final DescriptorIndexNode descriptorIndexNode) 48 | throws PSTException, IOException { 49 | super(theFile, descriptorIndexNode); 50 | } 51 | 52 | PSTAppointment(final PSTFile theFile, final DescriptorIndexNode folderIndexNode, final PSTTableBC table, 53 | final HashMap localDescriptorItems) { 54 | super(theFile, folderIndexNode, table, localDescriptorItems); 55 | } 56 | 57 | public boolean getSendAsICAL() { 58 | return (this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x00008200, PSTFile.PSETID_Appointment))); 59 | } 60 | 61 | public int getBusyStatus() { 62 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008205, PSTFile.PSETID_Appointment)); 63 | } 64 | 65 | public boolean getShowAsBusy() { 66 | return this.getBusyStatus() == 2; 67 | } 68 | 69 | public String getLocation() { 70 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008208, PSTFile.PSETID_Appointment)); 71 | } 72 | 73 | public Date getStartTime() { 74 | return this.getDateItem(this.pstFile.getNameToIdMapItem(0x0000820d, PSTFile.PSETID_Appointment)); 75 | } 76 | 77 | public PSTTimeZone getStartTimeZone() { 78 | return this.getTimeZoneItem(this.pstFile.getNameToIdMapItem(0x0000825e, PSTFile.PSETID_Appointment)); 79 | } 80 | 81 | public Date getEndTime() { 82 | return this.getDateItem(this.pstFile.getNameToIdMapItem(0x0000820e, PSTFile.PSETID_Appointment)); 83 | } 84 | 85 | public PSTTimeZone getEndTimeZone() { 86 | return this.getTimeZoneItem(this.pstFile.getNameToIdMapItem(0x0000825f, PSTFile.PSETID_Appointment)); 87 | } 88 | 89 | public PSTTimeZone getRecurrenceTimeZone() { 90 | final String desc = this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008234, PSTFile.PSETID_Appointment)); 91 | if (desc != null && desc.length() != 0) { 92 | final byte[] tzData = this 93 | .getBinaryItem(this.pstFile.getNameToIdMapItem(0x00008233, PSTFile.PSETID_Appointment)); 94 | if (tzData != null && tzData.length != 0) { 95 | return new PSTTimeZone(desc, tzData); 96 | } 97 | } 98 | return null; 99 | } 100 | 101 | public int getDuration() { 102 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008213, PSTFile.PSETID_Appointment)); 103 | } 104 | 105 | public int getColor() { 106 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008214, PSTFile.PSETID_Appointment)); 107 | } 108 | 109 | public boolean getSubType() { 110 | return (this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008215, PSTFile.PSETID_Appointment)) != 0); 111 | } 112 | 113 | public int getMeetingStatus() { 114 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008217, PSTFile.PSETID_Appointment)); 115 | } 116 | 117 | public int getResponseStatus() { 118 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008218, PSTFile.PSETID_Appointment)); 119 | } 120 | 121 | public boolean isRecurring() { 122 | return this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x00008223, PSTFile.PSETID_Appointment)); 123 | } 124 | 125 | public Date getRecurrenceBase() { 126 | return this.getDateItem(this.pstFile.getNameToIdMapItem(0x00008228, PSTFile.PSETID_Appointment)); 127 | } 128 | 129 | public int getRecurrenceType() { 130 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008231, PSTFile.PSETID_Appointment)); 131 | } 132 | 133 | public String getRecurrencePattern() { 134 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008232, PSTFile.PSETID_Appointment)); 135 | } 136 | 137 | public byte[] getRecurrenceStructure() { 138 | return this.getBinaryItem(this.pstFile.getNameToIdMapItem(0x00008216, PSTFile.PSETID_Appointment)); 139 | } 140 | 141 | public byte[] getTimezone() { 142 | return this.getBinaryItem(this.pstFile.getNameToIdMapItem(0x00008233, PSTFile.PSETID_Appointment)); 143 | } 144 | 145 | public String getAllAttendees() { 146 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008238, PSTFile.PSETID_Appointment)); 147 | } 148 | 149 | public String getToAttendees() { 150 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x0000823b, PSTFile.PSETID_Appointment)); 151 | } 152 | 153 | public String getCCAttendees() { 154 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x0000823c, PSTFile.PSETID_Appointment)); 155 | } 156 | 157 | public int getAppointmentSequence() { 158 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008201, PSTFile.PSETID_Appointment)); 159 | } 160 | 161 | // online meeting properties 162 | public boolean isOnlineMeeting() { 163 | return (this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x00008240, PSTFile.PSETID_Appointment))); 164 | } 165 | 166 | public int getNetMeetingType() { 167 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008241, PSTFile.PSETID_Appointment)); 168 | } 169 | 170 | public String getNetMeetingServer() { 171 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008242, PSTFile.PSETID_Appointment)); 172 | } 173 | 174 | public String getNetMeetingOrganizerAlias() { 175 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008243, PSTFile.PSETID_Appointment)); 176 | } 177 | 178 | public boolean getNetMeetingAutostart() { 179 | return (this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008245, PSTFile.PSETID_Appointment)) != 0); 180 | } 181 | 182 | public boolean getConferenceServerAllowExternal() { 183 | return (this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x00008246, PSTFile.PSETID_Appointment))); 184 | } 185 | 186 | public String getNetMeetingDocumentPathName() { 187 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008247, PSTFile.PSETID_Appointment)); 188 | } 189 | 190 | public String getNetShowURL() { 191 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008248, PSTFile.PSETID_Appointment)); 192 | } 193 | 194 | public Date getAttendeeCriticalChange() { 195 | return this.getDateItem(this.pstFile.getNameToIdMapItem(0x00000001, PSTFile.PSETID_Meeting)); 196 | } 197 | 198 | public Date getOwnerCriticalChange() { 199 | return this.getDateItem(this.pstFile.getNameToIdMapItem(0x0000001a, PSTFile.PSETID_Meeting)); 200 | } 201 | 202 | public String getConferenceServerPassword() { 203 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008249, PSTFile.PSETID_Appointment)); 204 | } 205 | 206 | public boolean getAppointmentCounterProposal() { 207 | return (this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x00008257, PSTFile.PSETID_Appointment))); 208 | } 209 | 210 | public boolean isSilent() { 211 | return (this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x00000004, PSTFile.PSETID_Meeting))); 212 | } 213 | 214 | public String getRequiredAttendees() { 215 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00000006, PSTFile.PSETID_Meeting)); 216 | } 217 | 218 | public int getLocaleId() { 219 | return this.getIntItem(0x3ff1); 220 | } 221 | 222 | public PSTGlobalObjectId getGlobalObjectId() { 223 | return new PSTGlobalObjectId( 224 | this.getBinaryItem(this.pstFile.getNameToIdMapItem(0x00000003, PSTFile.PSETID_Meeting))); 225 | } 226 | 227 | public PSTGlobalObjectId getCleanGlobalObjectId() { 228 | return new PSTGlobalObjectId( 229 | this.getBinaryItem(this.pstFile.getNameToIdMapItem(0x00000023, PSTFile.PSETID_Meeting))); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTAppointmentException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | 35 | package com.pff; 36 | 37 | import java.io.UnsupportedEncodingException; 38 | import java.util.Calendar; 39 | import java.util.Date; 40 | 41 | /** 42 | * Class containing information on exceptions to a recurring appointment 43 | * 44 | * @author Orin Eman 45 | * 46 | * 47 | */ 48 | public class PSTAppointmentException { 49 | 50 | // Access methods - return the value from the exception if 51 | // OverrideFlags say it's present, otherwise the value from the appointment. 52 | public String getSubject() { 53 | if ((this.OverrideFlags & 0x0001) != 0) { 54 | try { 55 | return new String(this.WideCharSubject, "UTF-16LE"); 56 | } catch (final UnsupportedEncodingException e) { 57 | e.printStackTrace(); 58 | } 59 | } 60 | 61 | return this.appt.getSubject(); 62 | } 63 | 64 | public int getMeetingType() { 65 | if ((this.OverrideFlags & 0x0002) != 0) { 66 | return this.MeetingType; 67 | } 68 | 69 | return this.appt.getMeetingStatus(); 70 | } 71 | 72 | public int getReminderDelta() { 73 | if ((this.OverrideFlags & 0x0004) != 0) { 74 | return this.ReminderDelta; 75 | } 76 | 77 | return this.appt.getReminderDelta(); 78 | } 79 | 80 | public boolean getReminderSet() { 81 | if ((this.OverrideFlags & 0x0008) != 0) { 82 | return this.ReminderSet; 83 | } 84 | 85 | return this.appt.getReminderSet(); 86 | } 87 | 88 | public String getLocation() { 89 | if ((this.OverrideFlags & 0x0010) != 0) { 90 | try { 91 | return new String(this.WideCharLocation, "UTF-16LE"); 92 | } catch (final UnsupportedEncodingException e) { 93 | e.printStackTrace(); 94 | } 95 | } 96 | 97 | return this.appt.getLocation(); 98 | } 99 | 100 | public int getBusyStatus() { 101 | if ((this.OverrideFlags & 0x0020) != 0) { 102 | return this.BusyStatus; 103 | } 104 | 105 | return this.appt.getBusyStatus(); 106 | } 107 | 108 | public boolean getSubType() { 109 | if ((this.OverrideFlags & 0x0080) != 0) { 110 | return this.SubType; 111 | } 112 | 113 | return this.appt.getSubType(); 114 | } 115 | 116 | public String getDescription() { 117 | if (this.embeddedMessage != null) { 118 | return this.embeddedMessage.getBodyPrefix(); 119 | } 120 | 121 | return null; 122 | } 123 | 124 | public Date getDTStamp() { 125 | Date ret = null; 126 | if (this.embeddedMessage != null) { 127 | ret = this.embeddedMessage.getOwnerCriticalChange(); 128 | } 129 | 130 | if (ret == null) { 131 | // Use current date/time 132 | final Calendar c = Calendar.getInstance(PSTTimeZone.utcTimeZone); 133 | ret = c.getTime(); 134 | } 135 | 136 | return ret; 137 | } 138 | 139 | public int getStartDateTime() { 140 | return this.StartDateTime; 141 | } 142 | 143 | public int getEndDateTime() { 144 | return this.EndDateTime; 145 | } 146 | 147 | public int getOriginalStartDate() { 148 | return this.OriginalStartDate; 149 | } 150 | 151 | public int getAppointmentSequence(final int def) { 152 | if (this.embeddedMessage == null) { 153 | return def; 154 | } 155 | return this.embeddedMessage.getAppointmentSequence(); 156 | } 157 | 158 | public int getImportance(final int def) { 159 | if (this.embeddedMessage == null) { 160 | return def; 161 | } 162 | return this.embeddedMessage.getImportance(); 163 | } 164 | 165 | public byte[] getSubjectBytes() { 166 | if ((this.OverrideFlags & 0x0010) != 0) { 167 | return this.Subject; 168 | } 169 | 170 | return null; 171 | } 172 | 173 | public byte[] getLocationBytes() { 174 | if ((this.OverrideFlags & 0x0010) != 0) { 175 | return this.Location; 176 | } 177 | 178 | return null; 179 | } 180 | 181 | public boolean attachmentsPresent() { 182 | if ((this.OverrideFlags & 0x0040) != 0 && this.Attachment == 0x00000001) { 183 | return true; 184 | } 185 | 186 | return false; 187 | } 188 | 189 | public boolean embeddedMessagePresent() { 190 | return this.embeddedMessage != null; 191 | } 192 | 193 | // 194 | // Allow access to an embedded message for 195 | // properties that don't have access methods here. 196 | // 197 | public PSTAppointment getEmbeddedMessage() { 198 | return this.embeddedMessage; 199 | } 200 | 201 | PSTAppointmentException(final byte[] recurrencePattern, int offset, final int writerVersion2, 202 | final PSTAppointment appt) { 203 | this.writerVersion2 = writerVersion2; 204 | final int initialOffset = offset; 205 | this.appt = appt; 206 | this.embeddedMessage = null; 207 | 208 | this.StartDateTime = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 209 | offset += 4; 210 | this.EndDateTime = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 211 | offset += 4; 212 | this.OriginalStartDate = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 213 | offset += 4; 214 | this.OverrideFlags = (short) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 2); 215 | offset += 2; 216 | 217 | if ((this.OverrideFlags & ARO_SUBJECT) != 0) { 218 | // @SuppressWarnings("unused") 219 | // short SubjectLength = 220 | // (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 221 | // offset, offset+2); 222 | offset += 2; 223 | final short SubjectLength2 = (short) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 224 | offset + 2); 225 | offset += 2; 226 | this.Subject = new byte[SubjectLength2]; 227 | System.arraycopy(recurrencePattern, offset, this.Subject, 0, SubjectLength2); 228 | offset += SubjectLength2; 229 | } 230 | 231 | if ((this.OverrideFlags & ARO_MEETINGTYPE) != 0) { 232 | this.MeetingType = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 233 | offset += 4; 234 | } 235 | 236 | if ((this.OverrideFlags & ARO_REMINDERDELTA) != 0) { 237 | this.ReminderDelta = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 238 | offset += 4; 239 | } 240 | 241 | if ((this.OverrideFlags & ARO_REMINDER) != 0) { 242 | this.ReminderSet = ((int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 243 | offset + 4) != 0); 244 | offset += 4; 245 | } 246 | 247 | if ((this.OverrideFlags & ARO_LOCATION) != 0) { 248 | // @SuppressWarnings("unused") 249 | // short LocationLength = 250 | // (short)PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 251 | // offset, offset+2); 252 | offset += 2; 253 | final short LocationLength2 = (short) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 254 | offset + 2); 255 | offset += 2; 256 | this.Location = new byte[LocationLength2]; 257 | System.arraycopy(recurrencePattern, offset, this.Location, 0, LocationLength2); 258 | offset += LocationLength2; 259 | } 260 | 261 | if ((this.OverrideFlags & ARO_BUSYSTATUS) != 0) { 262 | this.BusyStatus = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 263 | offset += 4; 264 | } 265 | 266 | if ((this.OverrideFlags & ARO_ATTACHMENT) != 0) { 267 | this.Attachment = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 268 | offset += 4; 269 | } 270 | 271 | if ((this.OverrideFlags & ARO_SUBTYPE) != 0) { 272 | this.SubType = ((int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4) != 0); 273 | offset += 4; 274 | } 275 | 276 | this.length = offset - initialOffset; 277 | } 278 | 279 | void ExtendedException(final byte[] recurrencePattern, int offset) { 280 | final int initialOffset = offset; 281 | 282 | if (this.writerVersion2 >= 0x00003009) { 283 | final int ChangeHighlightSize = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 284 | offset + 4); 285 | offset += 4; 286 | this.ChangeHighlightValue = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 287 | offset + 4); 288 | offset += ChangeHighlightSize; 289 | } 290 | 291 | int ReservedBlockEESize = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 292 | offset += 4 + ReservedBlockEESize; 293 | 294 | // See http://msdn.microsoft.com/en-us/library/cc979209(office.12).aspx 295 | if ((this.OverrideFlags & (ARO_SUBJECT | ARO_LOCATION)) != 0) { 296 | // Same as regular Exception structure? 297 | this.StartDateTime = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 298 | offset += 4; 299 | this.EndDateTime = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 300 | offset += 4; 301 | this.OriginalStartDate = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 302 | offset + 4); 303 | offset += 4; 304 | } 305 | 306 | if ((this.OverrideFlags & ARO_SUBJECT) != 0) { 307 | this.WideCharSubjectLength = (short) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 308 | offset + 2); 309 | offset += 2; 310 | this.WideCharSubject = new byte[this.WideCharSubjectLength * 2]; 311 | System.arraycopy(recurrencePattern, offset, this.WideCharSubject, 0, this.WideCharSubject.length); 312 | offset += this.WideCharSubject.length; 313 | /* 314 | * try { 315 | * String subject = new String(WideCharSubject, "UTF-16LE"); 316 | * System.out.printf("Exception Subject: %s\n", subject); 317 | * } catch (UnsupportedEncodingException e) { 318 | * e.printStackTrace(); 319 | * } 320 | * / 321 | **/ 322 | } 323 | 324 | if ((this.OverrideFlags & ARO_LOCATION) != 0) { 325 | this.WideCharLocationLength = (short) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 326 | offset + 2); 327 | offset += 2; 328 | this.WideCharLocation = new byte[this.WideCharLocationLength * 2]; 329 | System.arraycopy(recurrencePattern, offset, this.WideCharLocation, 0, this.WideCharLocation.length); 330 | offset += this.WideCharLocation.length; 331 | } 332 | 333 | // See http://msdn.microsoft.com/en-us/library/cc979209(office.12).aspx 334 | if ((this.OverrideFlags & (ARO_SUBJECT | ARO_LOCATION)) != 0) { 335 | ReservedBlockEESize = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 336 | offset += 4 + ReservedBlockEESize; 337 | } 338 | 339 | this.extendedLength = offset - initialOffset; 340 | } 341 | 342 | void setEmbeddedMessage(final PSTAppointment embeddedMessage) { 343 | this.embeddedMessage = embeddedMessage; 344 | } 345 | 346 | private final int writerVersion2; 347 | private int StartDateTime; 348 | private int EndDateTime; 349 | private int OriginalStartDate; 350 | private final short OverrideFlags; 351 | private byte[] Subject = null; 352 | private int MeetingType; 353 | private int ReminderDelta; 354 | private boolean ReminderSet; 355 | private byte[] Location = null; 356 | private int BusyStatus; 357 | private int Attachment; 358 | private boolean SubType; 359 | // private int AppointmentColor; // Reserved - don't read from the PST file 360 | @SuppressWarnings("unused") 361 | private int ChangeHighlightValue; 362 | private int WideCharSubjectLength = 0; 363 | private byte[] WideCharSubject = null; 364 | private int WideCharLocationLength = 0; 365 | private byte[] WideCharLocation = null; 366 | private PSTAppointment embeddedMessage = null; 367 | private final PSTAppointment appt; 368 | private final int length; 369 | private int extendedLength; 370 | 371 | // Length of this ExceptionInfo structure in the PST file 372 | int getLength() { 373 | return this.length; 374 | } 375 | 376 | // Length of this ExtendedException structure in the PST file 377 | int getExtendedLength() { 378 | return this.extendedLength; 379 | } 380 | 381 | static final short ARO_SUBJECT = 0x0001; 382 | static final short ARO_MEETINGTYPE = 0x0002; 383 | static final short ARO_REMINDERDELTA = 0x0004; 384 | static final short ARO_REMINDER = 0x0008; 385 | static final short ARO_LOCATION = 0x0010; 386 | static final short ARO_BUSYSTATUS = 0x0020; 387 | static final short ARO_ATTACHMENT = 0x0040; 388 | static final short ARO_SUBTYPE = 0x0080; 389 | } 390 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTAppointmentRecurrence.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | /* 37 | * import java.text.SimpleDateFormat; 38 | * / 39 | **/ 40 | 41 | import java.util.Calendar; 42 | import java.util.Date; 43 | import java.util.SimpleTimeZone; 44 | 45 | /** 46 | * Class containing recurrence information for a recurring appointment 47 | * 48 | * @author Orin Eman 49 | * 50 | * 51 | */ 52 | 53 | public class PSTAppointmentRecurrence { 54 | 55 | // Access methods 56 | 57 | public short getExceptionCount() { 58 | return this.ExceptionCount; 59 | } 60 | 61 | public PSTAppointmentException getException(final int i) { 62 | if (i < 0 || i >= this.ExceptionCount) { 63 | return null; 64 | } 65 | return this.Exceptions[i]; 66 | } 67 | 68 | public Calendar[] getDeletedInstanceDates() { 69 | return this.DeletedInstanceDates; 70 | } 71 | 72 | public Calendar[] getModifiedInstanceDates() { 73 | return this.ModifiedInstanceDates; 74 | } 75 | 76 | public short getCalendarType() { 77 | return this.CalendarType; 78 | } 79 | 80 | public short getPatternType() { 81 | return this.PatternType; 82 | } 83 | 84 | public int getPeriod() { 85 | return this.Period; 86 | } 87 | 88 | public int getPatternSpecific() { 89 | return this.PatternSpecific; 90 | } 91 | 92 | public int getFirstDOW() { 93 | return this.FirstDOW; 94 | } 95 | 96 | public int getPatternSpecificNth() { 97 | return this.PatternSpecificNth; 98 | } 99 | 100 | public int getFirstDateTime() { 101 | return this.FirstDateTime; 102 | } 103 | 104 | public int getEndType() { 105 | return this.EndType; 106 | } 107 | 108 | public int getOccurrenceCount() { 109 | return this.OccurrenceCount; 110 | } 111 | 112 | public int getEndDate() { 113 | return this.EndDate; 114 | } 115 | 116 | public int getStartTimeOffset() { 117 | return this.StartTimeOffset; 118 | } 119 | 120 | public PSTTimeZone getTimeZone() { 121 | return this.RecurrenceTimeZone; 122 | } 123 | 124 | public int getRecurFrequency() { 125 | return this.RecurFrequency; 126 | } 127 | 128 | public int getSlidingFlag() { 129 | return this.SlidingFlag; 130 | } 131 | 132 | public int getStartDate() { 133 | return this.StartDate; 134 | } 135 | 136 | public int getEndTimeOffset() { 137 | return this.EndTimeOffset; 138 | } 139 | 140 | public PSTAppointmentRecurrence(final byte[] recurrencePattern, final PSTAppointment appt, final PSTTimeZone tz) { 141 | this.RecurrenceTimeZone = tz; 142 | final SimpleTimeZone stz = this.RecurrenceTimeZone.getSimpleTimeZone(); 143 | 144 | // Read the structure 145 | this.RecurFrequency = (short) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 4, 6); 146 | this.PatternType = (short) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 6, 8); 147 | this.CalendarType = (short) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 8, 10); 148 | this.FirstDateTime = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 10, 14); 149 | this.Period = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 14, 18); 150 | this.SlidingFlag = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, 18, 22); 151 | int offset = 22; 152 | if (this.PatternType != 0) { 153 | this.PatternSpecific = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 154 | offset + 4); 155 | offset += 4; 156 | if (this.PatternType == 0x0003 || this.PatternType == 0x000B) { 157 | this.PatternSpecificNth = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 158 | offset + 4); 159 | offset += 4; 160 | } 161 | } 162 | this.EndType = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 163 | offset += 4; 164 | this.OccurrenceCount = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 165 | offset += 4; 166 | this.FirstDOW = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 167 | offset += 4; 168 | 169 | this.DeletedInstanceCount = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 170 | offset + 4); 171 | offset += 4; 172 | this.DeletedInstanceDates = new Calendar[this.DeletedInstanceCount]; 173 | for (int i = 0; i < this.DeletedInstanceCount; ++i) { 174 | this.DeletedInstanceDates[i] = PSTObject.apptTimeToUTC( 175 | (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4), 176 | this.RecurrenceTimeZone); 177 | offset += 4; 178 | /* 179 | * SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); 180 | * f.setTimeZone(RecurrenceTimeZone.getSimpleTimeZone()); 181 | * System.out.printf("DeletedInstanceDates[%d]: %s\n", i, 182 | * f.format(DeletedInstanceDates[i].getTime())); 183 | * / 184 | **/ 185 | } 186 | 187 | this.ModifiedInstanceCount = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 188 | offset + 4); 189 | offset += 4; 190 | this.ModifiedInstanceDates = new Calendar[this.ModifiedInstanceCount]; 191 | for (int i = 0; i < this.ModifiedInstanceCount; ++i) { 192 | this.ModifiedInstanceDates[i] = PSTObject.apptTimeToUTC( 193 | (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4), 194 | this.RecurrenceTimeZone); 195 | offset += 4; 196 | /* 197 | * SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); 198 | * f.setTimeZone(RecurrenceTimeZone.getSimpleTimeZone()); 199 | * System.out.printf("ModifiedInstanceDates[%d]: %s\n", i, 200 | * f.format(ModifiedInstanceDates[i].getTime())); 201 | * / 202 | **/ 203 | } 204 | 205 | this.StartDate = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 206 | offset += 4; 207 | this.EndDate = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 208 | offset += 4 + 4; // Skip ReaderVersion2 209 | 210 | this.writerVersion2 = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 211 | offset += 4; 212 | 213 | this.StartTimeOffset = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 214 | offset += 4; 215 | this.EndTimeOffset = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 4); 216 | offset += 4; 217 | this.ExceptionCount = (short) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, offset + 2); 218 | offset += 2; 219 | 220 | // Read exceptions 221 | this.Exceptions = new PSTAppointmentException[this.ExceptionCount]; 222 | for (int i = 0; i < this.ExceptionCount; ++i) { 223 | this.Exceptions[i] = new PSTAppointmentException(recurrencePattern, offset, this.writerVersion2, appt); 224 | offset += this.Exceptions[i].getLength(); 225 | } 226 | 227 | if ((offset + 4) <= recurrencePattern.length) { 228 | final int ReservedBlock1Size = (int) PSTObject.convertLittleEndianBytesToLong(recurrencePattern, offset, 229 | offset + 4); 230 | offset += 4 + (ReservedBlock1Size * 4); 231 | } 232 | 233 | // Read extended exception info 234 | for (int i = 0; i < this.ExceptionCount; ++i) { 235 | this.Exceptions[i].ExtendedException(recurrencePattern, offset); 236 | offset += this.Exceptions[i].getExtendedLength(); 237 | /* 238 | * Calendar c = 239 | * PSTObject.apptTimeToUTC(Exceptions[i].getStartDateTime(), 240 | * RecurrenceTimeZone); 241 | * System.out.printf("Exception[%d] start: %s\n", i, 242 | * FormatUTC(c.getTime())); 243 | * c = PSTObject.apptTimeToUTC(Exceptions[i].getEndDateTime(), 244 | * RecurrenceTimeZone); 245 | * System.out.printf("Exception[%d] end: %s\n", i, 246 | * FormatUTC(c.getTime())); 247 | * c = PSTObject.apptTimeToUTC(Exceptions[i].getOriginalStartDate(), 248 | * RecurrenceTimeZone); 249 | * System.out.printf("Exception[%d] original start: %s\n", i, 250 | * FormatUTC(c.getTime())); 251 | * / 252 | **/ 253 | } 254 | // Ignore any extra data - see 255 | // http://msdn.microsoft.com/en-us/library/cc979209(office.12).aspx 256 | 257 | // Get attachments, if any 258 | PSTAttachment[] attachments = new PSTAttachment[appt.getNumberOfAttachments()]; 259 | for (int i = 0; i < attachments.length; ++i) { 260 | try { 261 | attachments[i] = appt.getAttachment(i); 262 | } catch (final Exception e) { 263 | e.printStackTrace(); 264 | attachments[i] = null; 265 | } 266 | } 267 | 268 | PSTAppointment embeddedMessage = null; 269 | for (int i = 0; i < this.ExceptionCount; ++i) { 270 | try { 271 | // Match up an attachment to this exception... 272 | for (final PSTAttachment attachment : attachments) { 273 | if (attachment != null) { 274 | final PSTMessage message = attachment.getEmbeddedPSTMessage(); 275 | if (!(message instanceof PSTAppointment)) { 276 | continue; 277 | } 278 | embeddedMessage = (PSTAppointment) message; 279 | final Date replaceTime = embeddedMessage.getRecurrenceBase(); 280 | /* 281 | * SimpleDateFormat f = new 282 | * SimpleDateFormat("yyyyMMdd'T'HHmmss"); 283 | * f.setTimeZone(stz); 284 | * System.out.printf("Attachment[%d] time: %s\n", 285 | * iAttachment, f.format(replaceTime)); 286 | * / 287 | **/ 288 | final Calendar c = Calendar.getInstance(stz); 289 | c.setTime(replaceTime); 290 | if (c.get(Calendar.YEAR) == this.ModifiedInstanceDates[i].get(Calendar.YEAR) 291 | && c.get(Calendar.MONTH) == this.ModifiedInstanceDates[i].get(Calendar.MONTH) 292 | && c.get(Calendar.DAY_OF_MONTH) == this.ModifiedInstanceDates[i].get(Calendar.DAY_OF_MONTH)) { 293 | /* 294 | * System.out.println("\tEmbedded Message matched"); 295 | * / 296 | **/ 297 | 298 | this.Exceptions[i].setEmbeddedMessage(embeddedMessage); 299 | break; 300 | } 301 | } 302 | } 303 | } catch (final Exception e) { 304 | e.printStackTrace(); 305 | } 306 | } 307 | 308 | attachments = null; 309 | } 310 | 311 | /* 312 | * private String FormatUTC(Date date) { 313 | * SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); 314 | * f.setTimeZone(PSTTimeZone.utcTimeZone); 315 | * return f.format(date); 316 | * } 317 | * / 318 | **/ 319 | 320 | private final short RecurFrequency; 321 | private final short PatternType; 322 | private final short CalendarType; 323 | private final int FirstDateTime; 324 | private final int Period; 325 | private final int SlidingFlag; 326 | private int PatternSpecific; 327 | private int PatternSpecificNth; 328 | private final int EndType; 329 | private final int OccurrenceCount; 330 | private final int FirstDOW; 331 | private final int DeletedInstanceCount; 332 | private Calendar[] DeletedInstanceDates = null; 333 | private final int ModifiedInstanceCount; 334 | private Calendar[] ModifiedInstanceDates = null; 335 | private final int StartDate; 336 | private final int EndDate; 337 | // private int readerVersion2; 338 | private final int writerVersion2; 339 | private final int StartTimeOffset; 340 | private final int EndTimeOffset; 341 | private final short ExceptionCount; 342 | private PSTAppointmentException[] Exceptions = null; 343 | private PSTTimeZone RecurrenceTimeZone = null; 344 | } 345 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTAttachment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | import java.io.IOException; 37 | import java.io.InputStream; 38 | import java.util.Date; 39 | import java.util.HashMap; 40 | 41 | import java.io.ByteArrayInputStream; 42 | 43 | /** 44 | * Class containing attachment information. 45 | * 46 | * @author Richard Johnson 47 | */ 48 | public class PSTAttachment extends PSTObject { 49 | 50 | /** 51 | * Instantiates a new Pst attachment. 52 | * 53 | * @param theFile the the file 54 | * @param table the table 55 | * @param localDescriptorItems the local descriptor items 56 | */ 57 | PSTAttachment(final PSTFile theFile, final PSTTableBC table, 58 | final HashMap localDescriptorItems) { 59 | super(theFile, null, table, localDescriptorItems); 60 | } 61 | 62 | /** 63 | * Gets size. 64 | * 65 | * @return the size 66 | */ 67 | public int getSize() { 68 | return this.getIntItem(0x0e20); 69 | } 70 | 71 | @Override 72 | public Date getCreationTime() { 73 | return this.getDateItem(0x3007); 74 | } 75 | 76 | /** 77 | * Gets modification time. 78 | * 79 | * @return the modification time 80 | */ 81 | public Date getModificationTime() { 82 | return this.getDateItem(0x3008); 83 | } 84 | 85 | /** 86 | * Gets embedded pst message. 87 | * 88 | * @return the embedded pst message 89 | * @throws IOException the io exception 90 | * @throws PSTException the pst exception 91 | */ 92 | public PSTMessage getEmbeddedPSTMessage() throws IOException, PSTException { 93 | PSTNodeInputStream in = null; 94 | if (this.getIntItem(0x3705) == PSTAttachment.ATTACHMENT_METHOD_EMBEDDED) { 95 | final PSTTableBCItem item = this.items.get(0x3701); 96 | if (item.entryValueType == 0x0102) { 97 | if (!item.isExternalValueReference) { 98 | in = new PSTNodeInputStream(this.pstFile, item.data); 99 | } else { 100 | // We are in trouble! 101 | throw new PSTException("External reference in getEmbeddedPSTMessage()!\n"); 102 | } 103 | } else if (item.entryValueType == 0x000D) { 104 | final int descriptorItem = (int) PSTObject.convertLittleEndianBytesToLong(item.data, 0, 4); 105 | // PSTObject.printHexFormatted(item.data, true); 106 | final PSTDescriptorItem descriptorItemNested = this.localDescriptorItems.get(descriptorItem); 107 | in = new PSTNodeInputStream(this.pstFile, descriptorItemNested); 108 | if (descriptorItemNested.subNodeOffsetIndexIdentifier > 0) { 109 | this.localDescriptorItems 110 | .putAll(this.pstFile 111 | .getPSTDescriptorItems(descriptorItemNested.subNodeOffsetIndexIdentifier)); 112 | } 113 | /* 114 | * if ( descriptorItemNested != null ) { 115 | * try { 116 | * data = descriptorItemNested.getData(); 117 | * blockOffsets = descriptorItemNested.getBlockOffsets(); 118 | * } catch (Exception e) { 119 | * e.printStackTrace(); 120 | * 121 | * data = null; 122 | * blockOffsets = null; 123 | * } 124 | * } 125 | * 126 | */ 127 | } 128 | 129 | if (in == null) { 130 | return null; 131 | } 132 | 133 | try { 134 | final PSTTableBC attachmentTable = new PSTTableBC(in); 135 | return PSTObject.createAppropriatePSTMessageObject(this.pstFile, this.descriptorIndexNode, 136 | attachmentTable, this.localDescriptorItems); 137 | } catch (final PSTException e) { 138 | e.printStackTrace(); 139 | } 140 | return null; 141 | } 142 | return null; 143 | } 144 | 145 | /** 146 | * Gets file input stream. 147 | * 148 | * @return the file input stream 149 | * @throws IOException the io exception 150 | * @throws PSTException the pst exception 151 | */ 152 | public InputStream getFileInputStream() throws IOException, PSTException { 153 | 154 | final PSTTableBCItem attachmentDataObject = this.items.get(0x3701); 155 | 156 | if (null == attachmentDataObject) { 157 | return new ByteArrayInputStream(new byte[0]); 158 | } else if (attachmentDataObject.isExternalValueReference) { 159 | final PSTDescriptorItem descriptorItemNested = this.localDescriptorItems 160 | .get(attachmentDataObject.entryValueReference); 161 | return new PSTNodeInputStream(this.pstFile, descriptorItemNested); 162 | } else { 163 | // internal value references are never encrypted 164 | return new PSTNodeInputStream(this.pstFile, attachmentDataObject.data, false); 165 | } 166 | 167 | } 168 | 169 | /** 170 | * Gets filesize. 171 | * 172 | * @return the filesize 173 | * @throws PSTException the pst exception 174 | * @throws IOException the io exception 175 | */ 176 | public int getFilesize() throws PSTException, IOException { 177 | final PSTTableBCItem attachmentDataObject = this.items.get(0x3701); 178 | if (attachmentDataObject.isExternalValueReference) { 179 | final PSTDescriptorItem descriptorItemNested = this.localDescriptorItems 180 | .get(attachmentDataObject.entryValueReference); 181 | if (descriptorItemNested == null) { 182 | throw new PSTException( 183 | "missing attachment descriptor item for: " + attachmentDataObject.entryValueReference); 184 | } 185 | return descriptorItemNested.getDataSize(); 186 | } else { 187 | // raw attachment data, right there! 188 | return attachmentDataObject.data.length; 189 | } 190 | 191 | } 192 | 193 | // attachment properties 194 | 195 | /** 196 | * Attachment (short) filename ASCII or Unicode string 197 | * 198 | * @return the filename 199 | */ 200 | public String getFilename() { 201 | return this.getStringItem(0x3704); 202 | } 203 | 204 | /** 205 | * The constant ATTACHMENT_METHOD_NONE. 206 | */ 207 | public static final int ATTACHMENT_METHOD_NONE = 0; 208 | /** 209 | * The constant ATTACHMENT_METHOD_BY_VALUE. 210 | */ 211 | public static final int ATTACHMENT_METHOD_BY_VALUE = 1; 212 | /** 213 | * The constant ATTACHMENT_METHOD_BY_REFERENCE. 214 | */ 215 | public static final int ATTACHMENT_METHOD_BY_REFERENCE = 2; 216 | /** 217 | * The constant ATTACHMENT_METHOD_BY_REFERENCE_RESOLVE. 218 | */ 219 | public static final int ATTACHMENT_METHOD_BY_REFERENCE_RESOLVE = 3; 220 | /** 221 | * The constant ATTACHMENT_METHOD_BY_REFERENCE_ONLY. 222 | */ 223 | public static final int ATTACHMENT_METHOD_BY_REFERENCE_ONLY = 4; 224 | /** 225 | * The constant ATTACHMENT_METHOD_EMBEDDED. 226 | */ 227 | public static final int ATTACHMENT_METHOD_EMBEDDED = 5; 228 | /** 229 | * The constant ATTACHMENT_METHOD_OLE. 230 | */ 231 | public static final int ATTACHMENT_METHOD_OLE = 6; 232 | 233 | /** 234 | * Attachment method Integer 32-bit signed 0 => None (No attachment) 1 => By 235 | * value 2 => By reference 3 => By reference resolve 4 => By reference only 236 | * 5 => Embedded message 6 => OLE 237 | * 238 | * @return the attach method 239 | */ 240 | public int getAttachMethod() { 241 | return this.getIntItem(0x3705); 242 | } 243 | 244 | /** 245 | * Attachment size 246 | * 247 | * @return the attach size 248 | */ 249 | public int getAttachSize() { 250 | return this.getIntItem(0x0e20); 251 | } 252 | 253 | /** 254 | * Attachment number 255 | * 256 | * @return the attach num 257 | */ 258 | public int getAttachNum() { 259 | return this.getIntItem(0x0e21); 260 | } 261 | 262 | /** 263 | * Attachment long filename ASCII or Unicode string 264 | * 265 | * @return the long filename 266 | */ 267 | public String getLongFilename() { 268 | return this.getStringItem(0x3707); 269 | } 270 | 271 | /** 272 | * Attachment (short) pathname ASCII or Unicode string 273 | * 274 | * @return the pathname 275 | */ 276 | public String getPathname() { 277 | return this.getStringItem(0x3708); 278 | } 279 | 280 | /** 281 | * Attachment Position Integer 32-bit signed 282 | * 283 | * @return the rendering position 284 | */ 285 | public int getRenderingPosition() { 286 | return this.getIntItem(0x370b); 287 | } 288 | 289 | /** 290 | * Attachment long pathname ASCII or Unicode string 291 | * 292 | * @return the long pathname 293 | */ 294 | public String getLongPathname() { 295 | return this.getStringItem(0x370d); 296 | } 297 | 298 | /** 299 | * Attachment mime type ASCII or Unicode string 300 | * 301 | * @return the mime tag 302 | */ 303 | public String getMimeTag() { 304 | return this.getStringItem(0x370e); 305 | } 306 | 307 | /** 308 | * Attachment mime sequence 309 | * 310 | * @return the mime sequence 311 | */ 312 | public int getMimeSequence() { 313 | return this.getIntItem(0x3710); 314 | } 315 | 316 | /** 317 | * Attachment Content ID 318 | * 319 | * @return the content id 320 | */ 321 | public String getContentId() { 322 | return this.getStringItem(0x3712); 323 | } 324 | 325 | /** 326 | * Attachment not available in HTML 327 | * 328 | * @return the boolean 329 | */ 330 | public boolean isAttachmentInvisibleInHtml() { 331 | final int actionFlag = this.getIntItem(0x3714); 332 | return ((actionFlag & 0x1) > 0); 333 | } 334 | 335 | /** 336 | * Attachment not available in RTF 337 | * 338 | * @return the boolean 339 | */ 340 | public boolean isAttachmentInvisibleInRTF() { 341 | final int actionFlag = this.getIntItem(0x3714); 342 | return ((actionFlag & 0x2) > 0); 343 | } 344 | 345 | /** 346 | * Attachment is MHTML REF 347 | * 348 | * @return the boolean 349 | */ 350 | public boolean isAttachmentMhtmlRef() { 351 | final int actionFlag = this.getIntItem(0x3714); 352 | return ((actionFlag & 0x4) > 0); 353 | } 354 | 355 | /** 356 | * Attachment content disposition 357 | * 358 | * @return the attachment content disposition 359 | */ 360 | public String getAttachmentContentDisposition() { 361 | return this.getStringItem(0x3716); 362 | } 363 | 364 | } 365 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTByteFileContent.java: -------------------------------------------------------------------------------- 1 | package com.pff; 2 | 3 | import java.io.IOException; 4 | 5 | public class PSTByteFileContent extends PSTFileContent { 6 | 7 | protected byte[] content; 8 | protected int index; 9 | 10 | public PSTByteFileContent(final byte[] content) { 11 | this.content = content; 12 | this.index = 0; 13 | } 14 | 15 | public byte[] getBytes() { 16 | return this.content; 17 | } 18 | 19 | public void setBytes(final byte[] content) { 20 | this.content = content; 21 | } 22 | 23 | @Override 24 | public void seek(final long index) { 25 | this.index = (int) index; 26 | } 27 | 28 | @Override 29 | public long getFilePointer() { 30 | return this.index; 31 | } 32 | 33 | @Override 34 | public int read() { 35 | if (this.index >= this.content.length) { 36 | return -1; 37 | } 38 | return ((int) this.content[this.index++]) & 0xFF; 39 | } 40 | 41 | @Override 42 | public int read(final byte[] target) { 43 | if (this.index >= this.content.length) { 44 | return -1; 45 | } 46 | int targetindex = 0; 47 | while (targetindex < target.length & this.index < this.content.length) { 48 | target[targetindex++] = this.content[this.index++]; 49 | } 50 | return targetindex; 51 | } 52 | 53 | @Override 54 | public byte readByte() { 55 | return this.content[this.index++]; 56 | } 57 | 58 | @Override 59 | public void close() { 60 | // Do nothing 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTConversationIndex.java: -------------------------------------------------------------------------------- 1 | package com.pff; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.List; 6 | import java.util.UUID; 7 | 8 | /** 9 | * Class to hold decoded PidTagConversationIndex 10 | * 11 | * @author Nick Buller 12 | */ 13 | public class PSTConversationIndex { 14 | private static final int HUNDRED_NS_TO_MS = 1000; 15 | private static final int MINIMUM_HEADER_SIZE = 22; 16 | private static final int RESPONSE_LEVEL_SIZE = 5; 17 | 18 | private Date deliveryTime; 19 | private UUID guid; 20 | private List responseLevels = new ArrayList<>(); 21 | 22 | public Date getDeliveryTime() { 23 | return this.deliveryTime; 24 | } 25 | 26 | public UUID getGuid() { 27 | return this.guid; 28 | } 29 | 30 | public List getResponseLevels() { 31 | return this.responseLevels; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return this.guid + "@" + this.deliveryTime + " " + this.responseLevels.size() + " ResponseLevels"; 37 | } 38 | 39 | public class ResponseLevel { 40 | short deltaCode; 41 | long timeDelta; 42 | short random; 43 | 44 | public ResponseLevel(final short deltaCode, final long timeDelta, final short random) { 45 | this.deltaCode = deltaCode; 46 | this.timeDelta = timeDelta; 47 | this.random = random; 48 | } 49 | 50 | public short getDeltaCode() { 51 | return this.deltaCode; 52 | } 53 | 54 | public long getTimeDelta() { 55 | return this.timeDelta; 56 | } 57 | 58 | public short getRandom() { 59 | return this.random; 60 | } 61 | 62 | public Date withOffset(final Date anchorDate) { 63 | return new Date(anchorDate.getTime() + this.timeDelta); 64 | } 65 | } 66 | 67 | protected PSTConversationIndex(final byte[] rawConversationIndex) { 68 | if (rawConversationIndex != null && rawConversationIndex.length >= MINIMUM_HEADER_SIZE) { 69 | this.decodeHeader(rawConversationIndex); 70 | if (rawConversationIndex.length >= MINIMUM_HEADER_SIZE + RESPONSE_LEVEL_SIZE) { 71 | this.decodeResponseLevel(rawConversationIndex); 72 | } 73 | } 74 | } 75 | 76 | private void decodeHeader(final byte[] rawConversationIndex) { 77 | // According to the Spec the first byte is not included, but I believe 78 | // the spec is incorrect! 79 | // int reservedheaderMarker = (int) 80 | // PSTObject.convertBigEndianBytesToLong(rawConversationIndex, 0, 1); 81 | 82 | final long deliveryTimeHigh = PSTObject.convertBigEndianBytesToLong(rawConversationIndex, 0, 4); 83 | final long deliveryTimeLow = PSTObject.convertBigEndianBytesToLong(rawConversationIndex, 4, 6) << 16; 84 | this.deliveryTime = PSTObject.filetimeToDate((int) deliveryTimeHigh, (int) deliveryTimeLow); 85 | 86 | final long guidHigh = PSTObject.convertBigEndianBytesToLong(rawConversationIndex, 6, 14); 87 | final long guidLow = PSTObject.convertBigEndianBytesToLong(rawConversationIndex, 14, 22); 88 | 89 | this.guid = new UUID(guidHigh, guidLow); 90 | } 91 | 92 | private void decodeResponseLevel(final byte[] rawConversationIndex) { 93 | final int responseLevelCount = (rawConversationIndex.length - MINIMUM_HEADER_SIZE) / RESPONSE_LEVEL_SIZE; 94 | this.responseLevels = new ArrayList<>(responseLevelCount); 95 | 96 | for (int responseLevelIndex = 0, position = 22; responseLevelIndex < responseLevelCount; responseLevelIndex++, position += RESPONSE_LEVEL_SIZE) { 97 | 98 | final long responseLevelValue = PSTObject.convertBigEndianBytesToLong(rawConversationIndex, position, 99 | position + 5); 100 | final short deltaCode = (short) (responseLevelValue >> 39); 101 | final short random = (short) (responseLevelValue & 0xFF); 102 | 103 | // shift by 1 byte (remove the random) and mask off the deltaCode 104 | long deltaTime = (responseLevelValue >> 8) & 0x7FFFFFFF; 105 | 106 | if (deltaCode == 0) { 107 | deltaTime <<= 18; 108 | } else { 109 | deltaTime <<= 23; 110 | } 111 | 112 | deltaTime /= HUNDRED_NS_TO_MS; 113 | 114 | this.responseLevels.add(responseLevelIndex, new ResponseLevel(deltaCode, deltaTime, random)); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTDescriptorItem.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

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

16 | * --- 17 | *

18 | * This file is part of java-libpst. 19 | *

20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | *

25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | *

30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | */ 33 | package com.pff; 34 | 35 | import java.io.IOException; 36 | 37 | /** 38 | * The descriptor items contain information that describes a PST object. 39 | * This is like extended table entries, usually when the data cannot fit in a 40 | * traditional table item. 41 | * 42 | * This is an entry of type SLENTRY or SIENTRY 43 | * see [MS-PST]: Outlook Personal Folders (.pst) File Format 44 | * 45 | * @author Richard Johnson 46 | */ 47 | class PSTDescriptorItem { 48 | PSTDescriptorItem(final byte[] data, final int offset, final PSTFile pstFile, int entryType) { 49 | this.pstFile = pstFile; 50 | 51 | if (pstFile.getPSTFileType() == PSTFile.PST_TYPE_ANSI) { 52 | this.descriptorIdentifier = (int) PSTObject.convertLittleEndianBytesToLong(data, offset, offset + 4); 53 | this.offsetIndexIdentifier = ((int) PSTObject.convertLittleEndianBytesToLong(data, offset + 4, offset + 8)) 54 | & 0xfffffffe; 55 | if (entryType == PSTFile.SLBLOCK_ENTRY) 56 | this.subNodeOffsetIndexIdentifier = (int) PSTObject.convertLittleEndianBytesToLong(data, offset + 8, 57 | offset + 12) & 0xfffffffe; 58 | else 59 | this.subNodeOffsetIndexIdentifier = 0; 60 | } else { 61 | this.descriptorIdentifier = (int) PSTObject.convertLittleEndianBytesToLong(data, offset, offset + 4); 62 | this.offsetIndexIdentifier = ((int) PSTObject.convertLittleEndianBytesToLong(data, offset + 8, offset + 16)) 63 | & 0xfffffffe; 64 | if (entryType == PSTFile.SLBLOCK_ENTRY) 65 | this.subNodeOffsetIndexIdentifier = (int) PSTObject.convertLittleEndianBytesToLong(data, offset + 16, 66 | offset + 24) & 0xfffffffe; 67 | else 68 | this.subNodeOffsetIndexIdentifier = 0; 69 | } 70 | } 71 | 72 | public byte[] getData() throws IOException, PSTException { 73 | if (this.dataBlockData != null) { 74 | return this.dataBlockData; 75 | } 76 | 77 | final PSTNodeInputStream in = this.pstFile.readLeaf(this.offsetIndexIdentifier); 78 | final byte[] out = new byte[(int) in.length()]; 79 | in.readCompletely(out); 80 | this.dataBlockData = out; 81 | return this.dataBlockData; 82 | } 83 | 84 | public int[] getBlockOffsets() throws IOException, PSTException { 85 | if (this.dataBlockOffsets != null) { 86 | 87 | return this.dataBlockOffsets; 88 | } 89 | final Long[] offsets = this.pstFile.readLeaf(this.offsetIndexIdentifier).getBlockOffsets(); 90 | final int[] offsetsOut = new int[offsets.length]; 91 | for (int x = 0; x < offsets.length; x++) { 92 | offsetsOut[x] = offsets[x].intValue(); 93 | } 94 | return offsetsOut; 95 | } 96 | 97 | public int getDataSize() throws IOException, PSTException { 98 | return this.pstFile.getLeafSize(this.offsetIndexIdentifier); 99 | } 100 | 101 | // Public data 102 | int descriptorIdentifier; 103 | int offsetIndexIdentifier; 104 | int subNodeOffsetIndexIdentifier; 105 | 106 | // These are private to ensure that getData()/getBlockOffets() are used 107 | // private PSTFile.PSTFileBlock dataBlock = null; 108 | byte[] dataBlockData = null; 109 | int[] dataBlockOffsets = null; 110 | private final PSTFile pstFile; 111 | 112 | @Override 113 | public String toString() { 114 | return "PSTDescriptorItem\n" + " descriptorIdentifier: " + this.descriptorIdentifier + "\n" 115 | + " offsetIndexIdentifier: " + this.offsetIndexIdentifier + "\n" + " subNodeOffsetIndexIdentifier: " 116 | + this.subNodeOffsetIndexIdentifier + "\n"; 117 | 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTDistList.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | import java.io.IOException; 37 | import java.util.Arrays; 38 | import java.util.HashMap; 39 | 40 | /** 41 | * PST DistList for extracting Addresses from Distribution lists. 42 | * 43 | * @author Richard Johnson 44 | */ 45 | public class PSTDistList extends PSTMessage { 46 | 47 | /** 48 | * constructor. 49 | * 50 | * @param theFile 51 | * pst file 52 | * @param descriptorIndexNode 53 | * index of the list 54 | * @throws PSTException 55 | * on parsing error 56 | * @throws IOException 57 | * on data access error 58 | */ 59 | PSTDistList(final PSTFile theFile, final DescriptorIndexNode descriptorIndexNode) throws PSTException, IOException { 60 | super(theFile, descriptorIndexNode); 61 | } 62 | 63 | /** 64 | * Internal constructor for performance. 65 | * 66 | * @param theFile 67 | * pst file 68 | * @param folderIndexNode 69 | * index of the list 70 | * @param table 71 | * the PSTTableBC this object is represented by 72 | * @param localDescriptorItems 73 | * additional external items that represent 74 | * this object. 75 | */ 76 | PSTDistList(final PSTFile theFile, final DescriptorIndexNode folderIndexNode, final PSTTableBC table, 77 | final HashMap localDescriptorItems) { 78 | super(theFile, folderIndexNode, table, localDescriptorItems); 79 | } 80 | 81 | /** 82 | * Find the next two null bytes in an array given start. 83 | * 84 | * @param data 85 | * the array to search 86 | * @param start 87 | * the starting index 88 | * @return position of the next null char 89 | */ 90 | private int findNextNullChar(final byte[] data, int start) { 91 | for (; start < data.length; start += 2) { 92 | if (data[start] == 0 && data[start + 1] == 0) { 93 | break; 94 | } 95 | } 96 | return start; 97 | } 98 | 99 | /** 100 | * identifier for one-off entries. 101 | */ 102 | private final byte[] oneOffEntryIdUid = { (byte) 0x81, (byte) 0x2b, (byte) 0x1f, (byte) 0xa4, (byte) 0xbe, 103 | (byte) 0xa3, (byte) 0x10, (byte) 0x19, (byte) 0x9d, (byte) 0x6e, (byte) 0x00, (byte) 0xdd, (byte) 0x01, 104 | (byte) 0x0f, (byte) 0x54, (byte) 0x02 }; 105 | 106 | /** 107 | * identifier for wrapped entries. 108 | */ 109 | private final byte[] wrappedEntryIdUid = { (byte) 0xc0, (byte) 0x91, (byte) 0xad, (byte) 0xd3, (byte) 0x51, 110 | (byte) 0x9d, (byte) 0xcf, (byte) 0x11, (byte) 0xa4, (byte) 0xa9, (byte) 0x00, (byte) 0xaa, (byte) 0x00, 111 | (byte) 0x47, (byte) 0xfa, (byte) 0xa4 }; 112 | 113 | /** 114 | * Inner class to represent distribution list one-off entries. 115 | */ 116 | public class OneOffEntry { 117 | /** display name. */ 118 | private String displayName = ""; 119 | 120 | /** 121 | * @return display name 122 | */ 123 | public String getDisplayName() { 124 | return this.displayName; 125 | } 126 | 127 | /** address type (smtp). */ 128 | private String addressType = ""; 129 | 130 | /** 131 | * @return address type 132 | */ 133 | public String getAddressType() { 134 | return this.addressType; 135 | } 136 | 137 | /** email address. */ 138 | private String emailAddress = ""; 139 | 140 | /** 141 | * @return email address. 142 | */ 143 | public String getEmailAddress() { 144 | return this.emailAddress; 145 | } 146 | 147 | /** ending position of this object in the data array. */ 148 | private int pos = 0; 149 | 150 | /** 151 | * @return formatted record 152 | */ 153 | @Override 154 | public String toString() { 155 | return String.format("Display Name: %s\n" + "Address Type: %s\n" + "Email Address: %s\n", this.displayName, 156 | this.addressType, this.emailAddress); 157 | } 158 | } 159 | 160 | /** 161 | * Parse a one-off entry from this Distribution List. 162 | * 163 | * @param data 164 | * the item data 165 | * @param pos 166 | * the current position in the data. 167 | * @throws IOException 168 | * on string reading fail 169 | * @return the one-off entry 170 | */ 171 | private OneOffEntry parseOneOffEntry(final byte[] data, int pos) throws IOException { 172 | final int version = (int) PSTObject.convertLittleEndianBytesToLong(data, pos, pos + 2); 173 | pos += 2; 174 | 175 | // http://msdn.microsoft.com/en-us/library/ee202811(v=exchg.80).aspx 176 | final int additionalFlags = (int) PSTObject.convertLittleEndianBytesToLong(data, pos, pos + 2); 177 | pos += 2; 178 | 179 | final int pad = additionalFlags & 0x8000; 180 | final int mae = additionalFlags & 0x0C00; 181 | final int format = additionalFlags & 0x1E00; 182 | final int m = additionalFlags & 0x0100; 183 | final int u = additionalFlags & 0x0080; 184 | final int r = additionalFlags & 0x0060; 185 | final int l = additionalFlags & 0x0010; 186 | final int pad2 = additionalFlags & 0x000F; 187 | 188 | int stringEnd = this.findNextNullChar(data, pos); 189 | final byte[] displayNameBytes = new byte[stringEnd - pos]; 190 | System.arraycopy(data, pos, displayNameBytes, 0, displayNameBytes.length); 191 | final String displayName = new String(displayNameBytes, "UTF-16LE"); 192 | pos = stringEnd + 2; 193 | 194 | stringEnd = this.findNextNullChar(data, pos); 195 | final byte[] addressTypeBytes = new byte[stringEnd - pos]; 196 | System.arraycopy(data, pos, addressTypeBytes, 0, addressTypeBytes.length); 197 | final String addressType = new String(addressTypeBytes, "UTF-16LE"); 198 | pos = stringEnd + 2; 199 | 200 | stringEnd = this.findNextNullChar(data, pos); 201 | final byte[] emailAddressBytes = new byte[stringEnd - pos]; 202 | System.arraycopy(data, pos, emailAddressBytes, 0, emailAddressBytes.length); 203 | final String emailAddress = new String(emailAddressBytes, "UTF-16LE"); 204 | pos = stringEnd + 2; 205 | 206 | final OneOffEntry out = new OneOffEntry(); 207 | out.displayName = displayName; 208 | out.addressType = addressType; 209 | out.emailAddress = emailAddress; 210 | out.pos = pos; 211 | return out; 212 | } 213 | 214 | /** 215 | * Get an array of the members in this distribution list. 216 | * 217 | * @throws PSTException 218 | * on corrupted data 219 | * @throws IOException 220 | * on bad string reading 221 | * @return array of entries that can either be PSTDistList.OneOffEntry 222 | * or a PSTObject, generally PSTContact. 223 | */ 224 | public Object[] getDistributionListMembers() throws PSTException, IOException { 225 | final PSTTableBCItem item = this.items.get(this.pstFile.getNameToIdMapItem(0x8055, PSTFile.PSETID_Address)); 226 | Object[] out = {}; 227 | if (item != null) { 228 | int pos = 0; 229 | final int count = (int) PSTObject.convertLittleEndianBytesToLong(item.data, pos, pos + 4); 230 | out = new Object[count]; 231 | pos += 4; 232 | pos = (int) PSTObject.convertLittleEndianBytesToLong(item.data, pos, pos + 4); 233 | 234 | for (int x = 0; x < count; x++) { 235 | // http://msdn.microsoft.com/en-us/library/ee218661(v=exchg.80).aspx 236 | // http://msdn.microsoft.com/en-us/library/ee200559(v=exchg.80).aspx 237 | final int flags = (int) PSTObject.convertLittleEndianBytesToLong(item.data, pos, pos + 4); 238 | pos += 4; 239 | 240 | final byte[] guid = new byte[16]; 241 | System.arraycopy(item.data, pos, guid, 0, guid.length); 242 | pos += 16; 243 | 244 | if (Arrays.equals(guid, this.wrappedEntryIdUid)) { 245 | /* c3 */ 246 | final int entryType = item.data[pos] & 0x0F; 247 | final int entryAddressType = item.data[pos] & 0x70 >> 4; 248 | final boolean isOneOffEntryId = (item.data[pos] & 0x80) > 0; 249 | pos++; 250 | final int wrappedflags = (int) PSTObject.convertLittleEndianBytesToLong(item.data, pos, pos + 4); 251 | pos += 4; 252 | 253 | final byte[] guid2 = new byte[16]; 254 | System.arraycopy(item.data, pos, guid, 0, guid.length); 255 | pos += 16; 256 | 257 | final int descriptorIndex = (int) PSTObject.convertLittleEndianBytesToLong(item.data, pos, pos + 3); 258 | pos += 3; 259 | 260 | final byte empty = item.data[pos]; 261 | pos++; 262 | 263 | out[x] = PSTObject.detectAndLoadPSTObject(this.pstFile, descriptorIndex); 264 | 265 | } else if (Arrays.equals(guid, this.oneOffEntryIdUid)) { 266 | final OneOffEntry entry = this.parseOneOffEntry(item.data, pos); 267 | pos = entry.pos; 268 | out[x] = entry; 269 | } 270 | } 271 | } 272 | return out; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | /** 37 | * Simple exception for PST File related errors 38 | * 39 | * @author Richard Johnson 40 | */ 41 | public class PSTException extends Exception { 42 | /** 43 | * eclipse generated serial UID 44 | */ 45 | private static final long serialVersionUID = 4284698344354718143L; 46 | 47 | PSTException(final String error) { 48 | super(error); 49 | } 50 | 51 | PSTException(final String error, final Exception orig) { 52 | super(error, orig); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTFileContent.java: -------------------------------------------------------------------------------- 1 | package com.pff; 2 | 3 | import java.io.IOException; 4 | 5 | public abstract class PSTFileContent { 6 | public abstract void seek(long index) throws IOException; 7 | 8 | public abstract long getFilePointer() throws IOException; 9 | 10 | public abstract int read() throws IOException; 11 | 12 | public abstract int read(byte[] target) throws IOException; 13 | 14 | public final void readCompletely(final byte[] target) throws IOException { 15 | int read = this.read(target); 16 | // bail in common case 17 | if (read <= 0 || read == target.length) { 18 | return; 19 | } 20 | 21 | byte[] buffer = new byte[8192]; 22 | int offset = read; 23 | while (offset < target.length) { 24 | read = this.read(buffer); 25 | if (read <= 0) { 26 | break; 27 | } 28 | System.arraycopy(buffer, 0, target, offset, read); 29 | offset += read; 30 | } 31 | } 32 | 33 | public abstract byte readByte() throws IOException; 34 | 35 | public abstract void close() throws IOException; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTGlobalObjectId.java: -------------------------------------------------------------------------------- 1 | package com.pff; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.nio.ByteOrder; 5 | import java.util.Arrays; 6 | import java.util.Date; 7 | 8 | /** 9 | * Class to represent a decoded PidLidGlobalObjectId or 10 | * PidLidCleanGlobalObjectId (for Meeting Exceptions) 11 | * See [MS-OXOCAL] 12 | * "https://interoperability.blob.core.windows.net/files/MS-OXOCAL/%5bMS-OXOCAL%5d.pdf" 13 | * sections 2.2.1.27(PidLidGlobalObjectId) & 2.2.1.28(PidLidCleanGlobalObjectId) 14 | * 15 | * @author Nick Buller 16 | * NOTE: Following MS Doc for variable names so are exactly the same as 17 | * in MS-OXOCAL (not following Java conventions) 18 | */ 19 | public class PSTGlobalObjectId { 20 | final protected static byte[] ReferenceByteArrayID = { 0x04, 0x00, 0x00, 0x00, (byte) 0x82, 0x00, (byte) 0xe0, 0x00, 21 | 0x74, (byte) 0xc5, (byte) 0xb7, 0x10, 0x1a, (byte) 0x82, (byte) 0xe0, 0x08 }; 22 | final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); 23 | 24 | byte[] ByteArrayID = new byte[16]; 25 | byte YH; 26 | byte YL; 27 | byte M; 28 | byte D; 29 | int CreationTimeH; 30 | int CreationTimeL; 31 | Date CreationTime; 32 | long X = 0x0L; 33 | int Size; 34 | byte[] Data; 35 | 36 | public PSTGlobalObjectId(final byte[] pidData) { 37 | if (pidData.length < 32) { 38 | throw new AssertionError("pidDate is too short"); 39 | } 40 | 41 | System.arraycopy(pidData, 0, this.ByteArrayID, 0, ReferenceByteArrayID.length); 42 | 43 | if (!Arrays.equals(this.ByteArrayID, ReferenceByteArrayID)) { 44 | throw new AssertionError("ByteArrayID is incorrect"); 45 | } 46 | 47 | final ByteBuffer buffer = ByteBuffer 48 | .wrap(pidData, ReferenceByteArrayID.length, pidData.length - ReferenceByteArrayID.length) 49 | .order(ByteOrder.LITTLE_ENDIAN); 50 | 51 | this.YH = buffer.get(); 52 | this.YL = buffer.get(); 53 | this.M = buffer.get(); 54 | this.D = buffer.get(); 55 | this.CreationTimeL = buffer.getInt(); 56 | this.CreationTimeH = buffer.getInt(); 57 | this.CreationTime = PSTObject.filetimeToDate(this.CreationTimeH, this.CreationTimeL); 58 | this.X = buffer.getLong(); 59 | this.Size = buffer.getInt(); 60 | 61 | if (buffer.remaining() != this.Size) { 62 | throw new AssertionError("Incorrect remaining date in buffer to extract data"); 63 | } 64 | 65 | this.Data = new byte[buffer.remaining()]; 66 | buffer.get(this.Data, 0, buffer.remaining()); 67 | } 68 | 69 | public static String bytesToHex(final byte[] bytes) { 70 | final char[] hexChars = new char[bytes.length * 2]; 71 | for (int j = 0; j < bytes.length; j++) { 72 | final int v = bytes[j] & 0xFF; 73 | hexChars[j * 2] = hexArray[v >>> 4]; 74 | hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 75 | } 76 | return new String(hexChars); 77 | } 78 | 79 | protected int getYearHigh() { 80 | return this.YH; 81 | } 82 | 83 | protected int getYearLow() { 84 | return (this.YL & 0xFF); 85 | } 86 | 87 | public int getYear() { 88 | return (this.YH << 8) | (this.YL & 0xFF); 89 | } 90 | 91 | public int getMonth() { 92 | return this.M; 93 | } 94 | 95 | public int getDay() { 96 | return this.D; 97 | } 98 | 99 | public Date getCreationTime() { 100 | return this.CreationTime; 101 | } 102 | 103 | protected int getCreationTimeLow() { 104 | return this.CreationTimeL; 105 | } 106 | 107 | protected int getCreationTimeHigh() { 108 | return this.CreationTimeH; 109 | } 110 | 111 | public int getDataSize() { 112 | return this.Size; 113 | } 114 | 115 | public byte[] getData() { 116 | return this.Data; 117 | } 118 | 119 | @Override 120 | public String toString() { 121 | return "Byte Array ID[" + bytesToHex(this.ByteArrayID) + "] " + "Year [" + this.getYear() + "] " + "Month[" 122 | + this.M + "] " + "Day[" + this.D + "] CreationTime[" + this.CreationTime + "] " + "X[" + this.X + "] " 123 | + "Size[" + this.Size + "] " + "Data[" + bytesToHex(this.Data) + "]"; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTMessageStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | import java.io.IOException; 37 | import java.util.UUID; 38 | 39 | /** 40 | * Object that represents the message store. 41 | * Not much use other than to get the "name" of the PST file. 42 | * 43 | * @author Richard Johnson 44 | */ 45 | public class PSTMessageStore extends PSTObject { 46 | 47 | PSTMessageStore(final PSTFile theFile, final DescriptorIndexNode descriptorIndexNode) 48 | throws PSTException, IOException { 49 | super(theFile, descriptorIndexNode); 50 | } 51 | 52 | /** 53 | * Get the tag record key, unique to this pst 54 | * 55 | * @return the tag record key as uuid 56 | */ 57 | public UUID getTagRecordKeyAsUUID() { 58 | // attempt to find in the table. 59 | final int guidEntryType = 0x0ff9; 60 | if (this.items.containsKey(guidEntryType)) { 61 | final PSTTableBCItem item = this.items.get(guidEntryType); 62 | final int offset = 0; 63 | final byte[] bytes = item.data; 64 | final long mostSigBits = (PSTObject.convertLittleEndianBytesToLong(bytes, offset, offset + 4) << 32) 65 | | (PSTObject.convertLittleEndianBytesToLong(bytes, offset + 4, offset + 6) << 16) 66 | | PSTObject.convertLittleEndianBytesToLong(bytes, offset + 6, offset + 8); 67 | final long leastSigBits = PSTObject.convertBigEndianBytesToLong(bytes, offset + 8, offset + 16); 68 | return new UUID(mostSigBits, leastSigBits); 69 | } 70 | return null; 71 | } 72 | 73 | /** 74 | * get the message store display name 75 | */ 76 | @Override 77 | public String getDisplayName() { 78 | // attempt to find in the table. 79 | final int displayNameEntryType = 0x3001; 80 | if (this.items.containsKey(displayNameEntryType)) { 81 | return this.getStringItem(displayNameEntryType); 82 | // PSTTableBCItem item = 83 | // (PSTTableBCItem)this.items.get(displayNameEntryType); 84 | // return new String(item.getStringValue()); 85 | } 86 | return ""; 87 | } 88 | 89 | public String getDetails() { 90 | return this.items.toString(); 91 | } 92 | 93 | /** 94 | * Is this pst file is password protected. 95 | * 96 | * @throws PSTException 97 | * on corrupted pst 98 | * @throws IOException 99 | * on bad read 100 | * @return - true if protected,false otherwise 101 | * pstfile has the password stored against identifier 0x67FF. 102 | * if there is no password the value stored is 0x00000000. 103 | */ 104 | public boolean isPasswordProtected() throws PSTException, IOException { 105 | return (this.getLongItem(0x67FF) != 0); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTRAFileContent.java: -------------------------------------------------------------------------------- 1 | package com.pff; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.io.RandomAccessFile; 7 | 8 | public class PSTRAFileContent extends PSTFileContent { 9 | 10 | protected RandomAccessFile file; 11 | 12 | public PSTRAFileContent(final File file) throws FileNotFoundException { 13 | this.file = new RandomAccessFile(file, "r"); 14 | } 15 | 16 | public RandomAccessFile getFile() { 17 | return this.file; 18 | } 19 | 20 | public void setFile(final RandomAccessFile file) { 21 | this.file = file; 22 | } 23 | 24 | @Override 25 | public void seek(final long index) throws IOException { 26 | this.file.seek(index); 27 | } 28 | 29 | @Override 30 | public long getFilePointer() throws IOException { 31 | return this.file.getFilePointer(); 32 | } 33 | 34 | @Override 35 | public int read() throws IOException { 36 | return this.file.read(); 37 | } 38 | 39 | @Override 40 | public int read(final byte[] target) throws IOException { 41 | return this.file.read(target); 42 | } 43 | 44 | @Override 45 | public byte readByte() throws IOException { 46 | return this.file.readByte(); 47 | } 48 | 49 | @Override 50 | public void close() throws IOException { 51 | this.file.close(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTRecipient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | // import java.util.Date; 37 | import java.util.HashMap; 38 | 39 | /** 40 | * Class containing recipient information 41 | * 42 | * @author Orin Eman 43 | * 44 | * 45 | */ 46 | public class PSTRecipient { 47 | private final HashMap details; 48 | 49 | public static final int MAPI_TO = 1; 50 | public static final int MAPI_CC = 2; 51 | public static final int MAPI_BCC = 3; 52 | 53 | private PSTMessage message; 54 | 55 | PSTRecipient(PSTMessage message,final HashMap recipientDetails) { 56 | this.message=message; 57 | this.details = recipientDetails; 58 | } 59 | 60 | public String getDisplayName() { 61 | return this.getString(0x3001); 62 | } 63 | 64 | public int getRecipientType() { 65 | return this.getInt(0x0c15); 66 | } 67 | 68 | public String getEmailAddressType() { 69 | return this.getString(0x3002); 70 | } 71 | 72 | public String getEmailAddress() { 73 | return this.getString(0x3003); 74 | } 75 | 76 | public int getRecipientFlags() { 77 | return this.getInt(0x5ffd); 78 | } 79 | 80 | public int getRecipientOrder() { 81 | return this.getInt(0x5fdf); 82 | } 83 | 84 | public String getSmtpAddress() { 85 | // If the recipient address type is SMTP, 86 | // we can simply return the recipient address. 87 | final String addressType = this.getEmailAddressType(); 88 | if (addressType != null && addressType.equalsIgnoreCase("smtp")) { 89 | final String addr = this.getEmailAddress(); 90 | if (addr != null && addr.length() != 0) { 91 | return addr; 92 | } 93 | } 94 | // Otherwise, we have to hope the SMTP address is 95 | // present as the PidTagPrimarySmtpAddress property. 96 | return this.getString(0x39FE); 97 | } 98 | 99 | private String getString(final int id) { 100 | if (this.details.containsKey(id)) { 101 | final PSTTable7CItem item = this.details.get(id); 102 | return item.getStringValue(message.getStringCodepage()); 103 | } 104 | 105 | return ""; 106 | } 107 | 108 | /* 109 | * private boolean getBoolean(int id) { 110 | * if ( details.containsKey(id) ) { 111 | * PSTTable7CItem item = details.get(id); 112 | * if ( item.entryValueType == 0x000B ) 113 | * { 114 | * return (item.entryValueReference & 0xFF) == 0 ? false : true; 115 | * } 116 | * } 117 | * 118 | * return false; 119 | * } 120 | */ 121 | private int getInt(final int id) { 122 | if (this.details.containsKey(id)) { 123 | final PSTTable7CItem item = this.details.get(id); 124 | if (item.entryValueType == 0x0003) { 125 | return item.entryValueReference; 126 | } 127 | 128 | if (item.entryValueType == 0x0002) { 129 | final short s = (short) item.entryValueReference; 130 | return s; 131 | } 132 | } 133 | 134 | return 0; 135 | } 136 | 137 | /* 138 | * private Date getDate(int id) { 139 | * long lDate = 0; 140 | * 141 | * if ( details.containsKey(id) ) { 142 | * PSTTable7CItem item = details.get(id); 143 | * if ( item.entryValueType == 0x0040 ) { 144 | * int high = (int)PSTObject.convertLittleEndianBytesToLong(item.data, 4, 145 | * 8); 146 | * int low = (int)PSTObject.convertLittleEndianBytesToLong(item.data, 0, 4); 147 | * 148 | * return PSTObject.filetimeToDate(high, low); 149 | * } 150 | * } 151 | * return new Date(lDate); 152 | * } 153 | */ 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTRss.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | import java.io.IOException; 37 | import java.util.HashMap; 38 | 39 | /** 40 | * Object that represents a RSS item 41 | * 42 | * @author Richard Johnson 43 | */ 44 | public class PSTRss extends PSTMessage { 45 | 46 | /** 47 | * Instantiates a new Pst rss. 48 | * 49 | * @param theFile the the file 50 | * @param descriptorIndexNode the descriptor index node 51 | * @throws PSTException the pst exception 52 | * @throws IOException the io exception 53 | */ 54 | public PSTRss(final PSTFile theFile, final DescriptorIndexNode descriptorIndexNode) 55 | throws PSTException, IOException { 56 | super(theFile, descriptorIndexNode); 57 | } 58 | 59 | /** 60 | * Instantiates a new Pst rss. 61 | * 62 | * @param theFile the the file 63 | * @param folderIndexNode the folder index node 64 | * @param table the table 65 | * @param localDescriptorItems the local descriptor items 66 | */ 67 | public PSTRss(final PSTFile theFile, final DescriptorIndexNode folderIndexNode, final PSTTableBC table, 68 | final HashMap localDescriptorItems) { 69 | super(theFile, folderIndexNode, table, localDescriptorItems); 70 | } 71 | 72 | /** 73 | * Channel 74 | * 75 | * @return the post rss channel link 76 | */ 77 | public String getPostRssChannelLink() { 78 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008900, PSTFile.PSETID_PostRss)); 79 | } 80 | 81 | /** 82 | * Item link 83 | * 84 | * @return the post rss item link 85 | */ 86 | public String getPostRssItemLink() { 87 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008901, PSTFile.PSETID_PostRss)); 88 | } 89 | 90 | /** 91 | * Item hash Integer 32-bit signed 92 | * 93 | * @return the post rss item hash 94 | */ 95 | public int getPostRssItemHash() { 96 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008902, PSTFile.PSETID_PostRss)); 97 | } 98 | 99 | /** 100 | * Item GUID 101 | * 102 | * @return the post rss item guid 103 | */ 104 | public String getPostRssItemGuid() { 105 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008903, PSTFile.PSETID_PostRss)); 106 | } 107 | 108 | /** 109 | * Channel GUID 110 | * 111 | * @return the post rss channel 112 | */ 113 | public String getPostRssChannel() { 114 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008904, PSTFile.PSETID_PostRss)); 115 | } 116 | 117 | /** 118 | * Item XML 119 | * 120 | * @return the post rss item xml 121 | */ 122 | public String getPostRssItemXml() { 123 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008905, PSTFile.PSETID_PostRss)); 124 | } 125 | 126 | /** 127 | * Subscription 128 | * 129 | * @return the post rss subscription 130 | */ 131 | public String getPostRssSubscription() { 132 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008906, PSTFile.PSETID_PostRss)); 133 | } 134 | 135 | @Override 136 | public String toString() { 137 | return "Channel ASCII or Unicode string values: " + this.getPostRssChannelLink() + "\n" 138 | + "Item link ASCII or Unicode string values: " + this.getPostRssItemLink() + "\n" 139 | + "Item hash Integer 32-bit signed: " + this.getPostRssItemHash() + "\n" 140 | + "Item GUID ASCII or Unicode string values: " + this.getPostRssItemGuid() + "\n" 141 | + "Channel GUID ASCII or Unicode string values: " + this.getPostRssChannel() + "\n" 142 | + "Item XML ASCII or Unicode string values: " + this.getPostRssItemXml() + "\n" 143 | + "Subscription ASCII or Unicode string values: " + this.getPostRssSubscription(); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTTable.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | import java.io.IOException; 37 | import java.util.HashMap; 38 | 39 | /** 40 | * The PST Table is the workhorse of the whole system. 41 | * It allows for an item to be read and broken down into the individual 42 | * properties that it consists of. 43 | * For most PST Objects, it appears that only 7c and bc table types are used. 44 | * 45 | * @author Richard Johnson 46 | */ 47 | class PSTTable { 48 | 49 | protected String tableType; 50 | protected byte tableTypeByte; 51 | protected int hidUserRoot; 52 | 53 | protected Long[] arrayBlocks = null; 54 | 55 | // info from the b5 header 56 | protected int sizeOfItemKey; 57 | protected int sizeOfItemValue; 58 | protected int hidRoot; 59 | protected int numberOfKeys = 0; 60 | protected int numberOfIndexLevels = 0; 61 | 62 | private final PSTNodeInputStream in; 63 | 64 | // private int[][] rgbiAlloc = null; 65 | // private byte[] data = null; 66 | private HashMap subNodeDescriptorItems = null; 67 | 68 | protected String description = ""; 69 | 70 | protected PSTTable(final PSTNodeInputStream in, final HashMap subNodeDescriptorItems) 71 | throws PSTException, IOException { 72 | this.subNodeDescriptorItems = subNodeDescriptorItems; 73 | this.in = in; 74 | 75 | this.arrayBlocks = in.getBlockOffsets(); 76 | 77 | // the next two bytes should be the table type (bSig) 78 | // 0xEC is HN (Heap-on-Node) 79 | in.seek(0); 80 | final byte[] headdata = new byte[4]; 81 | in.readCompletely(headdata); 82 | if (headdata[2] != 0xffffffec) { 83 | // System.out.println(in.isEncrypted()); 84 | PSTObject.decode(headdata); 85 | PSTObject.printHexFormatted(headdata, true); 86 | throw new PSTException("Unable to parse table, bad table type..."); 87 | } 88 | 89 | this.tableTypeByte = headdata[3]; 90 | switch (this.tableTypeByte) { // bClientSig 91 | case 0x7c: // Table Context (TC/HN) 92 | this.tableType = "7c"; 93 | break; 94 | // case 0x9c: 95 | // tableType = "9c"; 96 | // valid = true; 97 | // break; 98 | // case 0xa5: 99 | // tableType = "a5"; 100 | // valid = true; 101 | // break; 102 | // case 0xac: 103 | // tableType = "ac"; 104 | // valid = true; 105 | // break; 106 | // case 0xFFFFFFb5: // BTree-on-Heap (BTH) 107 | // tableType = "b5"; 108 | // valid = true; 109 | // break; 110 | case 0xffffffbc: 111 | this.tableType = "bc"; // Property Context (PC/BTH) 112 | break; 113 | default: 114 | throw new PSTException( 115 | "Unable to parse table, bad table type. Unknown identifier: 0x" + Long.toHexString(headdata[3])); 116 | } 117 | 118 | this.hidUserRoot = (int) in.seekAndReadLong(4, 4); // hidUserRoot 119 | /* 120 | * System.out.printf("Table %s: hidUserRoot 0x%08X\n", tableType, 121 | * hidUserRoot); 122 | * / 123 | **/ 124 | 125 | // all tables should have a BTHHEADER at hnid == 0x20 126 | final NodeInfo headerNodeInfo = this.getNodeInfo(0x20); 127 | headerNodeInfo.in.seek(headerNodeInfo.startOffset); 128 | int headerByte = headerNodeInfo.in.read() & 0xFF; 129 | if (headerByte != 0xb5) { 130 | headerNodeInfo.in.seek(headerNodeInfo.startOffset); 131 | headerByte = headerNodeInfo.in.read() & 0xFF; 132 | headerNodeInfo.in.seek(headerNodeInfo.startOffset); 133 | final byte[] tmp = new byte[1024]; 134 | headerNodeInfo.in.readCompletely(tmp); 135 | PSTObject.printHexFormatted(tmp, true); 136 | // System.out.println(PSTObject.compEnc[headerByte]); 137 | throw new PSTException("Unable to parse table, can't find BTHHEADER header information: " + headerByte); 138 | } 139 | 140 | this.sizeOfItemKey = headerNodeInfo.in.read() & 0xFF; // Size of key in 141 | // key table 142 | this.sizeOfItemValue = headerNodeInfo.in.read() & 0xFF; // Size of value 143 | // in key table 144 | 145 | this.numberOfIndexLevels = headerNodeInfo.in.read() & 0xFF; 146 | if (this.numberOfIndexLevels != 0) { 147 | // System.out.println(this.tableType); 148 | // System.out.printf("Table with %d index levels\n", 149 | // numberOfIndexLevels); 150 | } 151 | // hidRoot = (int)PSTObject.convertLittleEndianBytesToLong(nodeInfo, 4, 152 | // 8); // hidRoot 153 | this.hidRoot = (int) headerNodeInfo.seekAndReadLong(4, 4); 154 | // System.out.println(hidRoot); 155 | // System.exit(0); 156 | /* 157 | * System.out.printf("Table %s: hidRoot 0x%08X\n", tableType, hidRoot); 158 | * / 159 | **/ 160 | this.description += "Table (" + this.tableType + ")\n" + "hidUserRoot: " + this.hidUserRoot + " - 0x" 161 | + Long.toHexString(this.hidUserRoot) + "\n" + "Size Of Keys: " + this.sizeOfItemKey + " - 0x" 162 | + Long.toHexString(this.sizeOfItemKey) + "\n" + "Size Of Values: " + this.sizeOfItemValue + " - 0x" 163 | + Long.toHexString(this.sizeOfItemValue) + "\n" + "hidRoot: " + this.hidRoot + " - 0x" 164 | + Long.toHexString(this.hidRoot) + "\n"; 165 | } 166 | 167 | protected void releaseRawData() { 168 | this.subNodeDescriptorItems = null; 169 | } 170 | 171 | /** 172 | * get the number of items stored in this table. 173 | * 174 | * @return row count 175 | */ 176 | public int getRowCount() { 177 | return this.numberOfKeys; 178 | } 179 | 180 | class NodeInfo { 181 | int startOffset; 182 | int endOffset; 183 | // byte[] data; 184 | PSTNodeInputStream in; 185 | 186 | NodeInfo(final int start, final int end, final PSTNodeInputStream in) throws PSTException { 187 | if (start > end) { 188 | throw new PSTException( 189 | String.format("Invalid NodeInfo parameters: start %1$d is greater than end %2$d", start, end)); 190 | } 191 | this.startOffset = start; 192 | this.endOffset = end; 193 | this.in = in; 194 | // this.data = data; 195 | } 196 | 197 | int length() { 198 | return this.endOffset - this.startOffset; 199 | } 200 | 201 | long seekAndReadLong(final long offset, final int length) throws IOException, PSTException { 202 | return this.in.seekAndReadLong(this.startOffset + offset, length); 203 | } 204 | } 205 | 206 | protected NodeInfo getNodeInfo(final int hnid) throws PSTException, IOException { 207 | 208 | // Zero-length node? 209 | if (hnid == 0) { 210 | return new NodeInfo(0, 0, this.in); 211 | } 212 | 213 | // Is it a subnode ID? 214 | if (this.subNodeDescriptorItems != null && this.subNodeDescriptorItems.containsKey(hnid)) { 215 | final PSTDescriptorItem item = this.subNodeDescriptorItems.get(hnid); 216 | // byte[] data; 217 | NodeInfo subNodeInfo = null; 218 | 219 | try { 220 | // data = item.getData(); 221 | final PSTNodeInputStream subNodeIn = new PSTNodeInputStream(this.in.getPSTFile(), item); 222 | subNodeInfo = new NodeInfo(0, (int) subNodeIn.length(), subNodeIn); 223 | } catch (final IOException e) { 224 | throw new PSTException(String.format("IOException reading subNode: 0x%08X", hnid)); 225 | } 226 | 227 | // return new NodeInfo(0, data.length, data); 228 | return subNodeInfo; 229 | } 230 | 231 | if ((hnid & 0x1F) != 0) { 232 | // Some kind of external node 233 | return null; 234 | } 235 | 236 | final int whichBlock = (hnid >>> 16); 237 | if (whichBlock > this.arrayBlocks.length) { 238 | // Block doesn't exist! 239 | String err = String.format("getNodeInfo: block doesn't exist! hnid = 0x%08X\n", hnid); 240 | err += String.format("getNodeInfo: block doesn't exist! whichBlock = 0x%08X\n", whichBlock); 241 | err += "\n" + (this.arrayBlocks.length); 242 | throw new PSTException(err); 243 | // return null; 244 | } 245 | 246 | // A normal node in a local heap 247 | final int index = (hnid & 0xFFFF) >> 5; 248 | int blockOffset = 0; 249 | if (whichBlock > 0) { 250 | blockOffset = this.arrayBlocks[whichBlock - 1].intValue(); 251 | } 252 | // Get offset of HN page map 253 | int iHeapNodePageMap = (int) this.in.seekAndReadLong(blockOffset, 2) + blockOffset; 254 | final int cAlloc = (int) this.in.seekAndReadLong(iHeapNodePageMap, 2); 255 | if (index >= cAlloc + 1) { 256 | throw new PSTException(String.format("getNodeInfo: node index doesn't exist! nid = 0x%08X\n", hnid)); 257 | // return null; 258 | } 259 | iHeapNodePageMap += (2 * index) + 2; 260 | final int start = (int) this.in.seekAndReadLong(iHeapNodePageMap, 2) + blockOffset; 261 | final int end = (int) this.in.seekAndReadLong(iHeapNodePageMap + 2, 2) + blockOffset; 262 | 263 | final NodeInfo out = new NodeInfo(start, end, this.in); 264 | return out; 265 | } 266 | 267 | } 268 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTTable7CItem.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | /** 37 | * Items found in the 7c tables 38 | * 39 | * @author Richard Johnson 40 | */ 41 | class PSTTable7CItem extends PSTTableItem { 42 | 43 | @Override 44 | public String toString() { 45 | return "7c Table Item: " + super.toString() + "\n"; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTTableBC.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | import java.util.HashMap; 37 | 38 | /** 39 | * The BC Table type. (Property Context) 40 | * Used by pretty much everything. 41 | * 42 | * @author Richard Johnson 43 | */ 44 | class PSTTableBC extends PSTTable { 45 | 46 | private final HashMap items = new HashMap<>(); 47 | 48 | private final StringBuilder descBuffer = new StringBuilder(); 49 | private boolean isDescNotYetInitiated = false; 50 | 51 | PSTTableBC(final PSTNodeInputStream in) throws PSTException, java.io.IOException { 52 | super(in, new HashMap()); 53 | // data = null; // No direct access to data! 54 | 55 | if (this.tableTypeByte != 0xffffffbc) { 56 | // System.out.println(Long.toHexString(this.tableTypeByte)); 57 | throw new PSTException("unable to create PSTTableBC, table does not appear to be a bc!"); 58 | } 59 | 60 | // go through each of the entries. 61 | // byte[] keyTableInfo = getNodeInfo(hidRoot); 62 | final NodeInfo keyTableInfoNodeInfo = this.getNodeInfo(this.hidRoot); 63 | final byte[] keyTableInfo = new byte[keyTableInfoNodeInfo.length()]; 64 | keyTableInfoNodeInfo.in.seek(keyTableInfoNodeInfo.startOffset); 65 | keyTableInfoNodeInfo.in.readCompletely(keyTableInfo); 66 | 67 | // PSTObject.printHexFormatted(keyTableInfo, true); 68 | // System.out.println(in.length()); 69 | // System.exit(0); 70 | this.numberOfKeys = keyTableInfo.length / (this.sizeOfItemKey + this.sizeOfItemValue); 71 | 72 | this.descBuffer.append("Number of entries: " + this.numberOfKeys + "\n"); 73 | 74 | // Read the key table 75 | int offset = 0; 76 | for (int x = 0; x < this.numberOfKeys; x++) { 77 | 78 | final PSTTableBCItem item = new PSTTableBCItem(); 79 | item.itemIndex = x; 80 | item.entryType = (int) PSTObject.convertLittleEndianBytesToLong(keyTableInfo, offset + 0, offset + 2); 81 | // item.entryType =(int)in.seekAndReadLong(offset, 2); 82 | item.entryValueType = (int) PSTObject.convertLittleEndianBytesToLong(keyTableInfo, offset + 2, offset + 4); 83 | // item.entryValueType = (int)in.seekAndReadLong(offset+2, 2); 84 | item.entryValueReference = (int) PSTObject.convertLittleEndianBytesToLong(keyTableInfo, offset + 4, 85 | offset + 8); 86 | // item.entryValueReference = (int)in.seekAndReadLong(offset+4, 4); 87 | 88 | // Data is in entryValueReference for all types <= 4 bytes long 89 | switch (item.entryValueType) { 90 | 91 | case 0x0002: // 16bit integer 92 | item.entryValueReference &= 0xFFFF; 93 | case 0x0003: // 32bit integer 94 | case 0x000A: // 32bit error code 95 | /* 96 | * System.out.printf("Integer%s: 0x%04X:%04X, %d\n", 97 | * (item.entryValueType == 0x0002) ? "16" : "32", 98 | * item.entryType, item.entryValueType, 99 | * item.entryValueReference); 100 | * / 101 | **/ 102 | case 0x0001: // Place-holder 103 | case 0x0004: // 32bit floating 104 | item.isExternalValueReference = true; 105 | break; 106 | 107 | case 0x000b: // Boolean - a single byte 108 | item.entryValueReference &= 0xFF; 109 | /* 110 | * System.out.printf("boolean: 0x%04X:%04X, %s\n", 111 | * item.entryType, item.entryValueType, 112 | * (item.entryValueReference == 0) ? "false" : "true"); 113 | * / 114 | **/ 115 | item.isExternalValueReference = true; 116 | break; 117 | 118 | case 0x000D: 119 | default: 120 | // Is it in the local heap? 121 | item.isExternalValueReference = true; // Assume not 122 | // System.out.println(item.entryValueReference); 123 | // byte[] nodeInfo = getNodeInfo(item.entryValueReference); 124 | final NodeInfo nodeInfoNodeInfo = this.getNodeInfo(item.entryValueReference); 125 | if (nodeInfoNodeInfo == null) { 126 | // It's an external reference that we don't deal with here. 127 | /* 128 | * System.out.printf("%s: %shid 0x%08X\n", 129 | * (item.entryValueType == 0x1f || item.entryValueType == 130 | * 0x1e) ? "String" : "Other", 131 | * PSTFile.getPropertyDescription(item.entryType, 132 | * item.entryValueType), 133 | * item.entryValueReference); 134 | * / 135 | **/ 136 | } else { 137 | // Make a copy of the data 138 | // item.data = new 139 | // byte[nodeInfo.endOffset-nodeInfo.startOffset]; 140 | final byte[] nodeInfo = new byte[nodeInfoNodeInfo.length()]; 141 | nodeInfoNodeInfo.in.seek(nodeInfoNodeInfo.startOffset); 142 | nodeInfoNodeInfo.in.readCompletely(nodeInfo); 143 | item.data = nodeInfo; // should be new array, so just use it 144 | // System.arraycopy(nodeInfo.data, nodeInfo.startOffset, 145 | // item.data, 0, item.data.length); 146 | item.isExternalValueReference = false; 147 | /* 148 | * if ( item.entryValueType == 0x1f || 149 | * item.entryValueType == 0x1e ) 150 | * { 151 | * try { 152 | * // if ( item.entryType == 0x0037 ) 153 | * { 154 | * String temp = new String(item.data, item.entryValueType 155 | * == 0x1E ? "UTF8" : "UTF-16LE"); 156 | * System.out.printf("String: 0x%04X:%04X, \"%s\"\n", 157 | * item.entryType, item.entryValueType, temp); 158 | * } 159 | * } catch (UnsupportedEncodingException e) { 160 | * e.printStackTrace(); 161 | * } 162 | * } 163 | * else 164 | * { 165 | * 166 | * System.out.printf("Other: 0x%04X:%04X, %d bytes\n", 167 | * item.entryType, item.entryValueType, item.data.length); 168 | * 169 | * } 170 | * / 171 | **/ 172 | } 173 | break; 174 | } 175 | 176 | offset = offset + 8; 177 | 178 | this.items.put(item.entryType, item); 179 | // description += item.toString()+"\n\n"; 180 | 181 | } 182 | 183 | this.releaseRawData(); 184 | } 185 | 186 | /** 187 | * get the items parsed out of this table. 188 | * 189 | * @return items 190 | */ 191 | public HashMap getItems() { 192 | return this.items; 193 | } 194 | 195 | @Override 196 | public String toString() { 197 | 198 | if (this.isDescNotYetInitiated) { 199 | this.isDescNotYetInitiated = false; 200 | 201 | for (final Integer curItem : this.items.keySet()) { 202 | this.descBuffer.append(this.items.get(curItem).toString() + "\n\n"); 203 | } 204 | // description += item.toString()+"\n\n"; 205 | } 206 | 207 | return this.description + this.descBuffer.toString(); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTTableBCItem.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | /** 37 | * Items within the BC Table 38 | * 39 | * @author Richard Johnson 40 | */ 41 | class PSTTableBCItem extends PSTTableItem { 42 | 43 | @Override 44 | public String toString() { 45 | return "Table Item: " + super.toString() + "\n"; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTTableItem.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | import java.io.UnsupportedEncodingException; 37 | import java.nio.charset.Charset; 38 | import java.text.SimpleDateFormat; 39 | import java.util.Date; 40 | import java.util.SimpleTimeZone; 41 | 42 | /** 43 | * Generic table item. 44 | * Provides some basic string functions 45 | * 46 | * @author Richard Johnson 47 | */ 48 | class PSTTableItem { 49 | 50 | public static final int VALUE_TYPE_PT_UNICODE = 0x1f; 51 | public static final int VALUE_TYPE_PT_STRING8 = 0x1e; 52 | public static final int VALUE_TYPE_PT_BIN = 0x102; 53 | 54 | public int itemIndex = 0; 55 | public int entryType = 0; 56 | public int entryValueType = 0; 57 | public int entryValueReference = 0; 58 | public byte[] data = new byte[0]; 59 | public boolean isExternalValueReference = false; 60 | 61 | public long getLongValue() { 62 | if (this.data.length > 0) { 63 | return PSTObject.convertLittleEndianBytesToLong(this.data); 64 | } 65 | return -1; 66 | } 67 | 68 | /** 69 | * Gets a string value of the data for a given codepage (charset name) 70 | * 71 | * @param codepage the codepage 72 | * @return the string value 73 | */ 74 | public String getStringValue(String codepage) { 75 | return this.getStringValue(this.entryValueType,codepage); 76 | } 77 | 78 | /** 79 | * Gets a string value of the data 80 | * 81 | * @param stringType the string type 82 | * @param codepage the codepage 83 | * @return string value 84 | */ 85 | public String getStringValue(final int stringType,String codepage) { 86 | 87 | if (stringType == VALUE_TYPE_PT_UNICODE) { 88 | // we are a nice little-endian unicode string. 89 | try { 90 | if (this.isExternalValueReference) { 91 | return "External string reference!"; 92 | } 93 | return new String(this.data, "UTF-16LE").trim(); 94 | } catch (final UnsupportedEncodingException e) { 95 | 96 | System.err.println("Error decoding string: " + this.data.toString()); 97 | return ""; 98 | } 99 | } 100 | 101 | if (stringType == VALUE_TYPE_PT_STRING8) { 102 | // System.out.println("Warning! decoding string8 without charset: 103 | // "+this.entryType + " - "+ Integer.toHexString(this.entryType)); 104 | return new String(this.data, Charset.forName(codepage)).trim(); 105 | } 106 | 107 | final StringBuffer outputBuffer = new StringBuffer(); 108 | /* 109 | * if ( stringType == VALUE_TYPE_PT_BIN) { 110 | * int theChar; 111 | * for (int x = 0; x < data.length; x++) { 112 | * theChar = data[x] & 0xFF; 113 | * outputBuffer.append((char)theChar); 114 | * } 115 | * } 116 | * else 117 | * / 118 | **/ 119 | { 120 | // we are not a normal string, give a hexish sort of output 121 | final StringBuffer hexOut = new StringBuffer(); 122 | for (final byte element : this.data) { 123 | final int valueChar = element & 0xff; 124 | if (Character.isLetterOrDigit((char) valueChar)) { 125 | outputBuffer.append((char) valueChar); 126 | outputBuffer.append(" "); 127 | } else { 128 | outputBuffer.append(". "); 129 | } 130 | final String hexValue = Long.toHexString(valueChar); 131 | hexOut.append(hexValue); 132 | hexOut.append(" "); 133 | if (hexValue.length() > 1) { 134 | outputBuffer.append(" "); 135 | } 136 | } 137 | outputBuffer.append("\n"); 138 | outputBuffer.append(" "); 139 | outputBuffer.append(hexOut); 140 | } 141 | 142 | return new String(outputBuffer); 143 | } 144 | 145 | @Override 146 | public String toString() { 147 | final String ret = PSTFile.getPropertyDescription(this.entryType, this.entryValueType); 148 | 149 | if (this.entryValueType == 0x000B) { 150 | return ret + (this.entryValueReference == 0 ? "false" : "true"); 151 | } 152 | 153 | if (this.isExternalValueReference) { 154 | // Either a true external reference, or entryValueReference contains 155 | // the actual data 156 | return ret + String.format("0x%08X (%d)", this.entryValueReference, this.entryValueReference); 157 | } 158 | 159 | if (this.entryValueType == 0x0005 || this.entryValueType == 0x0014) { 160 | // 64bit data 161 | if (this.data == null) { 162 | return ret + "no data"; 163 | } 164 | if (this.data.length == 8) { 165 | final long l = PSTObject.convertLittleEndianBytesToLong(this.data, 0, 8); 166 | return String.format("%s0x%016X (%d)", ret, l, l); 167 | } else { 168 | return String.format("%s invalid data length: %d", ret, this.data.length); 169 | } 170 | } 171 | 172 | if (this.entryValueType == 0x0040) { 173 | // It's a date... 174 | final int high = (int) PSTObject.convertLittleEndianBytesToLong(this.data, 4, 8); 175 | final int low = (int) PSTObject.convertLittleEndianBytesToLong(this.data, 0, 4); 176 | 177 | final Date d = PSTObject.filetimeToDate(high, low); 178 | this.dateFormatter.setTimeZone(utcTimeZone); 179 | return ret + this.dateFormatter.format(d); 180 | } 181 | 182 | if (this.entryValueType == 0x001F) { 183 | // Unicode string 184 | String s; 185 | try { 186 | s = new String(this.data, "UTF-16LE"); 187 | } catch (final UnsupportedEncodingException e) { 188 | System.err.println("Error decoding string: " + this.data.toString()); 189 | s = ""; 190 | } 191 | 192 | if (s.length() >= 2 && s.charAt(0) == 0x0001) { 193 | return String.format("%s [%04X][%04X]%s", ret, (short) s.charAt(0), (short) s.charAt(1), 194 | s.substring(2)); 195 | } 196 | 197 | return ret + s; 198 | } 199 | 200 | return ret + this.getStringValue("UTF-8"); 201 | } 202 | 203 | private final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); 204 | private static SimpleTimeZone utcTimeZone = new SimpleTimeZone(0, "UTC"); 205 | } 206 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTTask.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | import java.io.IOException; 37 | import java.util.Date; 38 | import java.util.HashMap; 39 | 40 | /** 41 | * Object that represents Task items 42 | * 43 | * @author Richard Johnson 44 | */ 45 | public class PSTTask extends PSTMessage { 46 | 47 | /** 48 | * Instantiates a new Pst task. 49 | * 50 | * @param theFile the the file 51 | * @param descriptorIndexNode the descriptor index node 52 | * @throws PSTException the pst exception 53 | * @throws IOException the io exception 54 | */ 55 | public PSTTask(final PSTFile theFile, final DescriptorIndexNode descriptorIndexNode) 56 | throws PSTException, IOException { 57 | super(theFile, descriptorIndexNode); 58 | } 59 | 60 | /** 61 | * Instantiates a new Pst task. 62 | * 63 | * @param theFile the the file 64 | * @param folderIndexNode the folder index node 65 | * @param table the table 66 | * @param localDescriptorItems the local descriptor items 67 | */ 68 | public PSTTask(final PSTFile theFile, final DescriptorIndexNode folderIndexNode, final PSTTableBC table, 69 | final HashMap localDescriptorItems) { 70 | super(theFile, folderIndexNode, table, localDescriptorItems); 71 | } 72 | 73 | /** 74 | * Status Integer 32-bit signed 0x0 => Not started 75 | * 76 | * @return the task status 77 | */ 78 | public int getTaskStatus() { 79 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008101, PSTFile.PSETID_Task)); 80 | } 81 | 82 | /** 83 | * Percent Complete Floating point double precision (64-bit) 84 | * 85 | * @return the percent complete 86 | */ 87 | public double getPercentComplete() { 88 | return this.getDoubleItem(this.pstFile.getNameToIdMapItem(0x00008102, PSTFile.PSETID_Task)); 89 | } 90 | 91 | /** 92 | * Is team task Boolean 93 | * 94 | * @return the boolean 95 | */ 96 | public boolean isTeamTask() { 97 | return this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x00008103, PSTFile.PSETID_Task)); 98 | } 99 | 100 | /** 101 | * Date completed Filetime 102 | * 103 | * @return the task date completed 104 | */ 105 | public Date getTaskDateCompleted() { 106 | return this.getDateItem(this.pstFile.getNameToIdMapItem(0x0000810f, PSTFile.PSETID_Task)); 107 | } 108 | 109 | /** 110 | * Actual effort in minutes Integer 32-bit signed 111 | * 112 | * @return the task actual effort 113 | */ 114 | public int getTaskActualEffort() { 115 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008110, PSTFile.PSETID_Task)); 116 | } 117 | 118 | /** 119 | * Total effort in minutes Integer 32-bit signed 120 | * 121 | * @return the task estimated effort 122 | */ 123 | public int getTaskEstimatedEffort() { 124 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008111, PSTFile.PSETID_Task)); 125 | } 126 | 127 | /** 128 | * Task version Integer 32-bit signed FTK: Access count 129 | * 130 | * @return the task version 131 | */ 132 | public int getTaskVersion() { 133 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008112, PSTFile.PSETID_Task)); 134 | } 135 | 136 | /** 137 | * Complete Boolean 138 | * 139 | * @return the boolean 140 | */ 141 | public boolean isTaskComplete() { 142 | return this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x0000811c, PSTFile.PSETID_Task)); 143 | } 144 | 145 | /** 146 | * Owner ASCII or Unicode string 147 | * 148 | * @return the task owner 149 | */ 150 | public String getTaskOwner() { 151 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x0000811f, PSTFile.PSETID_Task)); 152 | } 153 | 154 | /** 155 | * Delegator ASCII or Unicode string 156 | * 157 | * @return the task assigner 158 | */ 159 | public String getTaskAssigner() { 160 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008121, PSTFile.PSETID_Task)); 161 | } 162 | 163 | /** 164 | * Unknown ASCII or Unicode string 165 | * 166 | * @return the task last user 167 | */ 168 | public String getTaskLastUser() { 169 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008122, PSTFile.PSETID_Task)); 170 | } 171 | 172 | /** 173 | * Ordinal Integer 32-bit signed 174 | * 175 | * @return the task ordinal 176 | */ 177 | public int getTaskOrdinal() { 178 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008123, PSTFile.PSETID_Task)); 179 | } 180 | 181 | /** 182 | * Is recurring Boolean 183 | * 184 | * @return the boolean 185 | */ 186 | public boolean isTaskFRecurring() { 187 | return this.getBooleanItem(this.pstFile.getNameToIdMapItem(0x00008126, PSTFile.PSETID_Task)); 188 | } 189 | 190 | /** 191 | * Role ASCII or Unicode string 192 | * 193 | * @return the task role 194 | */ 195 | public String getTaskRole() { 196 | return this.getStringItem(this.pstFile.getNameToIdMapItem(0x00008127, PSTFile.PSETID_Task)); 197 | } 198 | 199 | /** 200 | * Ownership Integer 32-bit signed 201 | * 202 | * @return the task ownership 203 | */ 204 | public int getTaskOwnership() { 205 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x00008129, PSTFile.PSETID_Task)); 206 | } 207 | 208 | /** 209 | * Delegation State 210 | * 211 | * @return the acceptance state 212 | */ 213 | public int getAcceptanceState() { 214 | return this.getIntItem(this.pstFile.getNameToIdMapItem(0x0000812a, PSTFile.PSETID_Task)); 215 | } 216 | 217 | @Override 218 | public String toString() { 219 | return "Status Integer 32-bit signed 0x0 => Not started [TODO]: " + this.getTaskStatus() + "\n" 220 | + "Percent Complete Floating point double precision (64-bit): " + this.getPercentComplete() + "\n" 221 | + "Is team task Boolean: " + this.isTeamTask() + "\n" + "Start date Filetime: " + this.getTaskStartDate() 222 | + "\n" + "Due date Filetime: " + this.getTaskDueDate() + "\n" + "Date completed Filetime: " 223 | + this.getTaskDateCompleted() + "\n" + "Actual effort in minutes Integer 32-bit signed: " 224 | + this.getTaskActualEffort() + "\n" + "Total effort in minutes Integer 32-bit signed: " 225 | + this.getTaskEstimatedEffort() + "\n" + "Task version Integer 32-bit signed FTK: Access count: " 226 | + this.getTaskVersion() + "\n" + "Complete Boolean: " + this.isTaskComplete() + "\n" 227 | + "Owner ASCII or Unicode string: " + this.getTaskOwner() + "\n" + "Delegator ASCII or Unicode string: " 228 | + this.getTaskAssigner() + "\n" + "Unknown ASCII or Unicode string: " + this.getTaskLastUser() + "\n" 229 | + "Ordinal Integer 32-bit signed: " + this.getTaskOrdinal() + "\n" + "Is recurring Boolean: " 230 | + this.isTaskFRecurring() + "\n" + "Role ASCII or Unicode string: " + this.getTaskRole() + "\n" 231 | + "Ownership Integer 32-bit signed: " + this.getTaskOwnership() + "\n" + "Delegation State: " 232 | + this.getAcceptanceState(); 233 | 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/main/java/com/pff/PSTTimeZone.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Richard Johnson & Orin Eman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * --- 17 | * 18 | * This file is part of java-libpst. 19 | * 20 | * java-libpst is free software: you can redistribute it and/or modify 21 | * it under the terms of the GNU Lesser General Public License as published by 22 | * the Free Software Foundation, either version 3 of the License, or 23 | * (at your option) any later version. 24 | * 25 | * java-libpst is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | * GNU Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public License 31 | * along with java-libpst. If not, see . 32 | * 33 | */ 34 | package com.pff; 35 | 36 | import java.util.Calendar; 37 | import java.util.SimpleTimeZone; 38 | 39 | /** 40 | * Class containing time zone information 41 | * 42 | * @author Orin Eman 43 | * 44 | * 45 | */ 46 | 47 | public class PSTTimeZone { 48 | PSTTimeZone(final byte[] timeZoneData) { 49 | this.rule = null; 50 | this.name = ""; 51 | 52 | try { 53 | final int headerLen = (int) PSTObject.convertLittleEndianBytesToLong(timeZoneData, 2, 4); 54 | final int nameLen = 2 * (int) PSTObject.convertLittleEndianBytesToLong(timeZoneData, 6, 8); 55 | this.name = new String(timeZoneData, 8, nameLen, "UTF-16LE"); 56 | int ruleOffset = 8 + nameLen; 57 | final int nRules = (int) PSTObject.convertLittleEndianBytesToLong(timeZoneData, ruleOffset, ruleOffset + 2); 58 | 59 | ruleOffset = 4 + headerLen; 60 | for (int rule = 0; rule < nRules; ++rule) { 61 | // Is this rule the effective rule? 62 | final int flags = (int) PSTObject.convertLittleEndianBytesToLong(timeZoneData, ruleOffset + 4, 63 | ruleOffset + 6); 64 | if ((flags & 0x0002) != 0) { 65 | this.rule = new TZRule(timeZoneData, ruleOffset + 6); 66 | break; 67 | } 68 | ruleOffset += 66; 69 | } 70 | } catch (final Exception e) { 71 | System.err.printf("Exception reading timezone: %s\n", e.toString()); 72 | e.printStackTrace(); 73 | this.rule = null; 74 | this.name = ""; 75 | } 76 | } 77 | 78 | PSTTimeZone(String name, final byte[] timeZoneData) { 79 | this.name = name; 80 | this.rule = null; 81 | 82 | try { 83 | this.rule = new TZRule(new SYSTEMTIME(), timeZoneData, 0); 84 | } catch (final Exception e) { 85 | System.err.printf("Exception reading timezone: %s\n", e.toString()); 86 | e.printStackTrace(); 87 | this.rule = null; 88 | name = ""; 89 | } 90 | } 91 | 92 | public String getName() { 93 | return this.name; 94 | } 95 | 96 | public SimpleTimeZone getSimpleTimeZone() { 97 | if (this.simpleTimeZone != null) { 98 | return this.simpleTimeZone; 99 | } 100 | 101 | if (this.rule.startStandard.wMonth == 0) { 102 | // A time zone with no daylight savings time 103 | this.simpleTimeZone = new SimpleTimeZone((this.rule.lBias + this.rule.lStandardBias) * 60 * 1000, 104 | this.name); 105 | 106 | return this.simpleTimeZone; 107 | } 108 | 109 | final int startMonth = (this.rule.startDaylight.wMonth - 1) + Calendar.JANUARY; 110 | final int startDayOfMonth = (this.rule.startDaylight.wDay == 5) ? -1 111 | : ((this.rule.startDaylight.wDay - 1) * 7) + 1; 112 | final int startDayOfWeek = this.rule.startDaylight.wDayOfWeek + Calendar.SUNDAY; 113 | final int endMonth = (this.rule.startStandard.wMonth - 1) + Calendar.JANUARY; 114 | final int endDayOfMonth = (this.rule.startStandard.wDay == 5) ? -1 115 | : ((this.rule.startStandard.wDay - 1) * 7) + 1; 116 | final int endDayOfWeek = this.rule.startStandard.wDayOfWeek + Calendar.SUNDAY; 117 | final int savings = (this.rule.lStandardBias - this.rule.lDaylightBias) * 60 * 1000; 118 | 119 | this.simpleTimeZone = new SimpleTimeZone(-((this.rule.lBias + this.rule.lStandardBias) * 60 * 1000), this.name, 120 | startMonth, startDayOfMonth, -startDayOfWeek, 121 | (((((this.rule.startDaylight.wHour * 60) + this.rule.startDaylight.wMinute) * 60) 122 | + this.rule.startDaylight.wSecond) * 1000) + this.rule.startDaylight.wMilliseconds, 123 | endMonth, endDayOfMonth, -endDayOfWeek, 124 | (((((this.rule.startStandard.wHour * 60) + this.rule.startStandard.wMinute) * 60) 125 | + this.rule.startStandard.wSecond) * 1000) + this.rule.startStandard.wMilliseconds, 126 | savings); 127 | 128 | return this.simpleTimeZone; 129 | } 130 | 131 | public boolean isEqual(final PSTTimeZone rhs) { 132 | if (this.name.equalsIgnoreCase(rhs.name)) { 133 | if (this.rule.isEqual(rhs.rule)) { 134 | return true; 135 | } 136 | 137 | System.err.printf("Warning: different timezones with the same name: %s\n", this.name); 138 | } 139 | return false; 140 | } 141 | 142 | public SYSTEMTIME getStart() { 143 | return this.rule.dtStart; 144 | } 145 | 146 | public int getBias() { 147 | return this.rule.lBias; 148 | } 149 | 150 | public int getStandardBias() { 151 | return this.rule.lStandardBias; 152 | } 153 | 154 | public int getDaylightBias() { 155 | return this.rule.lDaylightBias; 156 | } 157 | 158 | public SYSTEMTIME getDaylightStart() { 159 | return this.rule.startDaylight; 160 | } 161 | 162 | public SYSTEMTIME getStandardStart() { 163 | return this.rule.startStandard; 164 | } 165 | 166 | public class SYSTEMTIME { 167 | 168 | SYSTEMTIME() { 169 | this.wYear = 0; 170 | this.wMonth = 0; 171 | this.wDayOfWeek = 0; 172 | this.wDay = 0; 173 | this.wHour = 0; 174 | this.wMinute = 0; 175 | this.wSecond = 0; 176 | this.wMilliseconds = 0; 177 | } 178 | 179 | SYSTEMTIME(final byte[] timeZoneData, final int offset) { 180 | this.wYear = (short) (PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset, offset + 2) & 0x7FFF); 181 | this.wMonth = (short) (PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset + 2, offset + 4) 182 | & 0x7FFF); 183 | this.wDayOfWeek = (short) (PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset + 4, offset + 6) 184 | & 0x7FFF); 185 | this.wDay = (short) (PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset + 6, offset + 8) 186 | & 0x7FFF); 187 | this.wHour = (short) (PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset + 8, offset + 10) 188 | & 0x7FFF); 189 | this.wMinute = (short) (PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset + 10, offset + 12) 190 | & 0x7FFF); 191 | this.wSecond = (short) (PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset + 12, offset + 14) 192 | & 0x7FFF); 193 | this.wMilliseconds = (short) (PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset + 14, 194 | offset + 16) & 0x7FFF); 195 | } 196 | 197 | boolean isEqual(final SYSTEMTIME rhs) { 198 | return this.wYear == rhs.wYear && this.wMonth == rhs.wMonth && this.wDayOfWeek == rhs.wDayOfWeek 199 | && this.wDay == rhs.wDay && this.wHour == rhs.wHour && this.wMinute == rhs.wMinute 200 | && this.wSecond == rhs.wSecond && this.wMilliseconds == rhs.wMilliseconds; 201 | } 202 | 203 | public short wYear; 204 | public short wMonth; 205 | public short wDayOfWeek; 206 | public short wDay; 207 | public short wHour; 208 | public short wMinute; 209 | public short wSecond; 210 | public short wMilliseconds; 211 | } 212 | 213 | /** 214 | * A static copy of the UTC time zone, available for others to use 215 | */ 216 | public static SimpleTimeZone utcTimeZone = new SimpleTimeZone(0, "UTC"); 217 | 218 | private class TZRule { 219 | 220 | TZRule(final SYSTEMTIME dtStart, final byte[] timeZoneData, final int offset) { 221 | this.dtStart = dtStart; 222 | this.InitBiases(timeZoneData, offset); 223 | @SuppressWarnings("unused") 224 | final short wStandardYear = (short) PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset + 12, 225 | offset + 14); 226 | this.startStandard = new SYSTEMTIME(timeZoneData, offset + 14); 227 | @SuppressWarnings("unused") 228 | final short wDaylightYear = (short) PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset + 30, 229 | offset + 32); 230 | this.startDaylight = new SYSTEMTIME(timeZoneData, offset + 32); 231 | } 232 | 233 | TZRule(final byte[] timeZoneData, final int offset) { 234 | this.dtStart = new SYSTEMTIME(timeZoneData, offset); 235 | this.InitBiases(timeZoneData, offset + 16); 236 | this.startStandard = new SYSTEMTIME(timeZoneData, offset + 28); 237 | this.startDaylight = new SYSTEMTIME(timeZoneData, offset + 44); 238 | } 239 | 240 | private void InitBiases(final byte[] timeZoneData, final int offset) { 241 | this.lBias = (int) PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset, offset + 4); 242 | this.lStandardBias = (int) PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset + 4, offset + 8); 243 | this.lDaylightBias = (int) PSTObject.convertLittleEndianBytesToLong(timeZoneData, offset + 8, offset + 12); 244 | } 245 | 246 | boolean isEqual(final TZRule rhs) { 247 | return this.dtStart.isEqual(rhs.dtStart) && this.lBias == rhs.lBias 248 | && this.lStandardBias == rhs.lStandardBias && this.lDaylightBias == rhs.lDaylightBias 249 | && this.startStandard.isEqual(rhs.startStandard) && this.startDaylight.isEqual(rhs.startDaylight); 250 | } 251 | 252 | SYSTEMTIME dtStart; 253 | int lBias; 254 | int lStandardBias; 255 | int lDaylightBias; 256 | SYSTEMTIME startStandard; 257 | SYSTEMTIME startDaylight; 258 | } 259 | 260 | private String name; 261 | private TZRule rule; 262 | private SimpleTimeZone simpleTimeZone = null; 263 | } 264 | -------------------------------------------------------------------------------- /src/main/java/example/Test.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import java.util.Vector; 4 | 5 | import com.pff.PSTException; 6 | import com.pff.PSTFile; 7 | import com.pff.PSTFolder; 8 | import com.pff.PSTMessage; 9 | 10 | public class Test { 11 | public static void main(final String[] args) { 12 | new Test(args[0]); 13 | } 14 | 15 | public Test(final String filename) { 16 | try { 17 | final PSTFile pstFile = new PSTFile(filename); 18 | System.out.println(pstFile.getMessageStore().getDisplayName()); 19 | this.processFolder(pstFile.getRootFolder()); 20 | } catch (final Exception err) { 21 | err.printStackTrace(); 22 | } 23 | } 24 | 25 | int depth = -1; 26 | 27 | public void processFolder(final PSTFolder folder) throws PSTException, java.io.IOException { 28 | this.depth++; 29 | // the root folder doesn't have a display name 30 | if (this.depth > 0) { 31 | this.printDepth(); 32 | System.out.println(folder.getDisplayName()); 33 | } 34 | 35 | // go through the folders... 36 | if (folder.hasSubfolders()) { 37 | final Vector childFolders = folder.getSubFolders(); 38 | for (final PSTFolder childFolder : childFolders) { 39 | this.processFolder(childFolder); 40 | } 41 | } 42 | 43 | // and now the emails for this folder 44 | if (folder.getContentCount() > 0) { 45 | this.depth++; 46 | PSTMessage email = (PSTMessage) folder.getNextChild(); 47 | while (email != null) { 48 | if (!email.getMessageClass().equals("IPM.Note")) { 49 | this.printDepth(); 50 | System.out.println("Email: [" + email.getMessageClass() + "]" + email.getDescriptorNodeId() + " - " + email.getSubject()); 51 | } 52 | email = (PSTMessage) folder.getNextChild(); 53 | } 54 | this.depth--; 55 | } 56 | this.depth--; 57 | } 58 | 59 | public void printDepth() { 60 | for (int x = 0; x < this.depth - 1; x++) { 61 | System.out.print(" | "); 62 | } 63 | System.out.print(" |- "); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/example/TestGui.rs: -------------------------------------------------------------------------------- 1 | EmailTableModel 2 | TestGui 3 | -------------------------------------------------------------------------------- /src/main/resources/InternetCodepages.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2010 Richard Johnson & Orin Eman 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # --- 16 | # 17 | # This file is part of java-libpst. 18 | # 19 | # java-libpst is free software: you can redistribute it and/or modify 20 | # it under the terms of the GNU Lesser General Public License as published by 21 | # the Free Software Foundation, either version 3 of the License, or 22 | # (at your option) any later version. 23 | # 24 | # java-libpst is distributed in the hope that it will be useful, 25 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 | # GNU Lesser General Public License for more details. 28 | # 29 | # You should have received a copy of the GNU Lesser General Public License 30 | # along with java-libpst. If not, see . 31 | 32 | 28596=iso-8859-6 33 | 1256=windows-1256 34 | 28594=iso-8859-4 35 | 1257=windows-1257 36 | 28592=iso-8859-2 37 | 1250=windows-1250 38 | 936=gb2312 39 | 52936=hz-gb-2312 40 | 54936=gb18030 41 | 950=big5 42 | 28595=iso-8859-5 43 | 20866=koi8-r 44 | 21866=koi8-u 45 | 1251=windows-1251 46 | 28597=iso-8859-7 47 | 1253=windows-1253 48 | 38598=iso-8859-8-i 49 | 1255=windows-1255 50 | 51932=euc-jp 51 | 50220=iso-2022-jp 52 | 50221=csISO2022JP 53 | 932=iso-2022-jp 54 | 949=ks_c_5601-1987 55 | 51949=euc-kr 56 | 28593=iso-8859-3 57 | 28605=iso-8859-15 58 | 874=windows-874 59 | 28599=iso-8859-9 60 | 1254=windows-1254 61 | 65000=utf-7 62 | 65001=utf-8 63 | 20127=us-ascii 64 | 1258=windows-1258 65 | 28591=iso-8859-1 66 | 1252=Windows-1252 67 | -------------------------------------------------------------------------------- /src/main/resources/PIDShortID.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjohnsondev/java-libpst/f158a64acf2a0e46ac3bd699bc7a5a8da6c40d26/src/main/resources/PIDShortID.csv -------------------------------------------------------------------------------- /src/test/java/com/pff/AppointmentTest.java: -------------------------------------------------------------------------------- 1 | package com.pff; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.URISyntaxException; 6 | import java.net.URL; 7 | import java.util.Calendar; 8 | import java.util.Arrays; 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.junit.runners.JUnit4; 13 | 14 | /** 15 | * Tests for {@link PSTAppointment}. 16 | * 17 | * @author Richard Johnson 18 | */ 19 | @RunWith(JUnit4.class) 20 | public class AppointmentTest { 21 | 22 | /** 23 | * Test we can access appointments from the PST. 24 | */ 25 | @Test 26 | public final void testGetDistList() 27 | throws PSTException, IOException, URISyntaxException { 28 | URL dirUrl = ClassLoader.getSystemResource("dist-list.pst"); 29 | PSTFile pstFile = new PSTFile(new File(dirUrl.toURI())); 30 | PSTAppointment appt = (PSTAppointment) PSTObject.detectAndLoadPSTObject(pstFile, 2097348); 31 | PSTAppointmentRecurrence r = new PSTAppointmentRecurrence( 32 | appt.getRecurrenceStructure(), appt, appt.getRecurrenceTimeZone()); 33 | 34 | 35 | Assert.assertEquals( 36 | "Has 3 deleted items (1 removed, 2 changed)", 37 | 3, 38 | r.getDeletedInstanceDates().length); 39 | 40 | Assert.assertEquals( 41 | "Number of Exceptions", 42 | 2, 43 | r.getExceptionCount()); 44 | 45 | String d = r.getException(0).getDescription().trim(); 46 | Assert.assertEquals("correct app desc", "This is the appointment at 9", d); 47 | 48 | Calendar c = PSTObject.apptTimeToCalendar( 49 | r.getException(0).getStartDateTime()); 50 | Assert.assertEquals( 51 | "First exception correct hour", 52 | 9, 53 | c.get(Calendar.HOUR)); 54 | 55 | d = r.getException(1).getDescription().trim(); 56 | Assert.assertEquals("correct app desc", "This is the one at 10", d); 57 | 58 | c = PSTObject.apptTimeToCalendar( 59 | r.getException(1).getStartDateTime()); 60 | Assert.assertEquals( 61 | "Second exception correct hour", 62 | 10, 63 | c.get(Calendar.HOUR)); 64 | 65 | //System.out.println(r.getExceptionCount()); 66 | //System.out.println(r.getException(0).getDTStamp()); 67 | 68 | //for (int x = 0; x < r.getDeletedInstanceDates().length; x++) { 69 | // System.out.println(r.getDeletedInstanceDates()[x]); 70 | //} 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /src/test/java/com/pff/DistListTest.java: -------------------------------------------------------------------------------- 1 | package com.pff; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.URISyntaxException; 6 | import java.net.URL; 7 | import java.util.HashSet; 8 | import java.util.Arrays; 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.junit.runners.JUnit4; 13 | 14 | /** 15 | * Tests for {@link PSTDistList}. 16 | * 17 | * @author Richard Johnson 18 | */ 19 | @RunWith(JUnit4.class) 20 | public class DistListTest { 21 | 22 | /** 23 | * Test we can retrieve distribution lists from the PST. 24 | */ 25 | @Test 26 | public final void testGetDistList() 27 | throws PSTException, IOException, URISyntaxException { 28 | URL dirUrl = ClassLoader.getSystemResource("dist-list.pst"); 29 | PSTFile pstFile = new PSTFile(new File(dirUrl.toURI())); 30 | PSTDistList obj = (PSTDistList)PSTObject.detectAndLoadPSTObject(pstFile, 2097188); 31 | Object[] members = obj.getDistributionListMembers(); 32 | Assert.assertEquals("Correct number of members", members.length, 3); 33 | int numberOfContacts = 0; 34 | int numberOfOneOffRecords = 0; 35 | HashSet emailAddresses = new HashSet(); 36 | HashSet displayNames = new HashSet(); 37 | for (Object member : members) { 38 | if (member instanceof PSTContact) { 39 | PSTContact contact = (PSTContact)member; 40 | Assert.assertEquals("Contact email address", 41 | contact.getEmail1EmailAddress(), 42 | "contact1@rjohnson.id.au"); 43 | numberOfContacts++; 44 | } else { 45 | PSTDistList.OneOffEntry entry = (PSTDistList.OneOffEntry)member; 46 | emailAddresses.add(entry.getEmailAddress()); 47 | displayNames.add(entry.getDisplayName()); 48 | numberOfOneOffRecords++; 49 | } 50 | } 51 | Assert.assertEquals("Correct number of members", members.length, 3); 52 | Assert.assertEquals("Contains all display names", 53 | displayNames, 54 | new HashSet(Arrays.asList( 55 | new String[] {"dist name 2", 56 | "dist name 1"}))); 57 | Assert.assertEquals("Contains all email addresses", 58 | emailAddresses, 59 | new HashSet(Arrays.asList( 60 | new String[] {"dist1@rjohnson.id.au", 61 | "dist2@rjohnson.id.au"}))); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/com/pff/PSTGlobalObjectIdTest.java: -------------------------------------------------------------------------------- 1 | package com.pff; 2 | 3 | import java.util.Date; 4 | 5 | import org.junit.Test; 6 | 7 | import static org.hamcrest.CoreMatchers.equalTo; 8 | import static org.junit.Assert.assertThat; 9 | 10 | /** 11 | * Author: Nick Buller 12 | */ 13 | public class PSTGlobalObjectIdTest { 14 | 15 | @Test 16 | public void unpackValid() { 17 | // Global Object ID: 18 | // Byte Array ID = cb: 16 lpb: 040000008200E00074C5B7101A82E008 19 | // Year: 0x0000 = 0 20 | // Month: 0x00 = 0 = 0x0 21 | // Day: 0x00 = 0 22 | // Creation Time = 0x01D04F6F:0xA226A470 = 01:50:07.415 PM 23/02/2015 23 | // X: 0x00000000:0x00000000 24 | // Size: 0x10 = 16 25 | // Data = cb: 16 lpb: 086DFAD3919FD44089199898CDCF4DC2 26 | byte[] objectId = { 27 | 0x04, 0x00, 0x00, 0x00, (byte) 0x82, 0x00, (byte) 0xE0, 0x00, 0x74, (byte) 0xC5, (byte) 0xB7, 0x10, 0x1A, (byte) 0x82, (byte) 0xE0, 0x08, // Byte Array ID 28 | 0x07, // Year Hi 29 | (byte) 0xde, // Year Low 30 | 0x0b, // Month 31 | 0x14, // Day 32 | 0x70, (byte) 0xA4, 0x26, (byte) 0xA2, 0x6F, 0x4F, (byte) 0xD0, 0x01, // Creation Time 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // X 34 | 0x10, 0x00, 0x00, 0x00, // Size 35 | 0x08, 0x6D, (byte) 0xFA, (byte) 0xD3, (byte) 0x91, (byte) 0x9F, (byte) 0xD4, 0x40, (byte) 0x89, 0x19, (byte) 0x98, (byte) 0x98, (byte) 0xCD, (byte) 0xCF, 0x4D, (byte) 0xC2 // Data 36 | }; 37 | 38 | PSTGlobalObjectId object = new PSTGlobalObjectId(objectId); 39 | 40 | assertThat("Validate YearHi is correct", object.getYearLow(), equalTo(0x00DE)); 41 | assertThat("Validate YearLow is correct", object.getYearHigh(), equalTo(0x7)); 42 | assertThat("Validate Year is correct", object.getYear(), equalTo(2014)); 43 | assertThat("Validate Day is correct", object.getDay(), equalTo(20)); 44 | assertThat("Validate Month is correct", object.getMonth(), equalTo(11)); 45 | assertThat("Validate CreationTimeLow is correct", object.getCreationTimeLow(), equalTo(0xA226A470)); 46 | assertThat("Validate CreationTimeHigh is correct", object.getCreationTimeHigh(), equalTo(0x01D04F6F)); 47 | assertThat("Validate CreationTime is correct", object.getCreationTime().getTime(), equalTo(new Date(1424699407415L).getTime())); 48 | assertThat("Validate Size is correct", object.getDataSize(), equalTo(16)); 49 | assertThat("Validate Size of date matches actual data size", object.getData().length, equalTo(object.getDataSize())); 50 | assertThat("Validate Date is correct", PSTGlobalObjectId.bytesToHex(object.getData()), equalTo("086DFAD3919FD44089199898CDCF4DC2")); 51 | } 52 | 53 | @Test(expected = AssertionError.class) 54 | public void unpackWithInvalidIdSignature() { 55 | byte[] objectId = { 56 | 0x04, 0x00, 0x00, 0x00, (byte) 0x82, 0x00, (byte) 0xE0, 0x00, 0x74, (byte) 0xC5, (byte) 0xB7, 0x10, 0x1A, (byte) 0x82, (byte) 0xE0, 0x00, // Byte Array ID (last byte 00 rather then 08 57 | 0x07, // Year Hi 58 | (byte) 0xde, // Year Low 59 | 0x0b, // Month 60 | 0x14, // Day 61 | 0x70, (byte) 0xA4, 0x26, (byte) 0xA2, 0x6F, 0x4F, (byte) 0xD0, 0x01, // Creation Time 62 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // X 63 | 0x10, 0x00, 0x00, 0x00, // Size 64 | 0x08, 0x6D, (byte) 0xFA, (byte) 0xD3, (byte) 0x91, (byte) 0x9F, (byte) 0xD4, 0x40, (byte) 0x89, 0x19, (byte) 0x98, (byte) 0x98, (byte) 0xCD, (byte) 0xCF, 0x4D, (byte) 0xC2 // Data 65 | }; 66 | 67 | PSTGlobalObjectId object = new PSTGlobalObjectId(objectId); 68 | } 69 | 70 | @Test(expected = AssertionError.class) 71 | public void unpackWithInvalidIdData() { 72 | byte[] objectId = { 73 | 0x04, 0x00, 0x00, 0x00, (byte) 0x82, 0x00, (byte) 0xE0, 0x00, 0x74, (byte) 0xC5, (byte) 0xB7, 0x10, 0x1A, (byte) 0x82, (byte) 0xE0, 0x08, // Byte Array ID 74 | 0x07, // Year Hi 75 | (byte) 0xde, // Year Low 76 | 0x0b, // Month 77 | 0x14, // Day 78 | 0x70, (byte) 0xA4, 0x26, (byte) 0xA2, 0x6F, 0x4F, (byte) 0xD0, 0x01, // Creation Time 79 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // X 80 | 0x10, 0x00, 0x00, 0x00, // Size 81 | 0x08, 0x6D, (byte) 0xFA, (byte) 0xD3, (byte) 0x91, (byte) 0x9F, (byte) 0xD4, 0x40, (byte) 0x89, 0x19, (byte) 0x98, (byte) 0x98, (byte) 0xCD, (byte) 0xCF, 0x4D // Data (missing last byte) 82 | }; 83 | 84 | PSTGlobalObjectId object = new PSTGlobalObjectId(objectId); 85 | } 86 | 87 | @Test(expected = AssertionError.class) 88 | public void unpackWithInvalidIdDataLength() { 89 | byte[] objectId = { 90 | 0x04, 0x00, 0x00, 0x00, (byte) 0x82, 0x00, (byte) 0xE0, 0x00, 0x74, (byte) 0xC5, (byte) 0xB7, 0x10, 0x1A, (byte) 0x82, (byte) 0xE0, 0x08, // Byte Array ID 91 | 0x07, // Year Hi 92 | (byte) 0xde, // Year Low 93 | 0x0b, // Month 94 | 0x14, // Day 95 | 0x70, (byte) 0xA4, 0x26, (byte) 0xA2, 0x6F, 0x4F, (byte) 0xD0, 0x01, // Creation Time 96 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // X 97 | 0x10, 0x10, 0x00, 0x00, // Size 98 | 0x08, 0x6D, (byte) 0xFA, (byte) 0xD3, (byte) 0x91, (byte) 0x9F, (byte) 0xD4, 0x40, (byte) 0x89, 0x19, (byte) 0x98, (byte) 0x98, (byte) 0xCD, (byte) 0xCF, 0x4D, (byte) 0xC2 // Data 99 | }; 100 | 101 | PSTGlobalObjectId object = new PSTGlobalObjectId(objectId); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/com/pff/PasswordTest.java: -------------------------------------------------------------------------------- 1 | package com.pff; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.URISyntaxException; 6 | import java.net.URL; 7 | import java.util.HashSet; 8 | import java.util.Arrays; 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.junit.runners.JUnit4; 13 | 14 | /** 15 | * Tests for {@link PSTDistList}. 16 | * 17 | * @author Richard Johnson 18 | */ 19 | @RunWith(JUnit4.class) 20 | public class PasswordTest { 21 | 22 | /** 23 | * Test for password protectedness. 24 | */ 25 | @Test 26 | public final void testPasswordProtected() 27 | throws PSTException, IOException, URISyntaxException { 28 | URL dirUrl = ClassLoader.getSystemResource("passworded.pst"); 29 | PSTFile pstFile = new PSTFile(new File(dirUrl.toURI())); 30 | Assert.assertEquals("Is password protected", 31 | pstFile.getMessageStore().isPasswordProtected(), 32 | true); 33 | } 34 | 35 | /** 36 | * Test for non-password protectedness. 37 | */ 38 | @Test 39 | public final void testNotPasswordProtected() 40 | throws PSTException, IOException, URISyntaxException { 41 | URL dirUrl = ClassLoader.getSystemResource("dist-list.pst"); 42 | PSTFile pstFile = new PSTFile(new File(dirUrl.toURI())); 43 | Assert.assertEquals("Is password protected", 44 | pstFile.getMessageStore().isPasswordProtected(), 45 | false); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/com/pff/Version36Test.java: -------------------------------------------------------------------------------- 1 | package com.pff; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.URISyntaxException; 6 | import java.net.URL; 7 | import java.util.*; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.junit.runners.JUnit4; 12 | 13 | @RunWith(JUnit4.class) 14 | public class Version36Test { 15 | 16 | @Test 17 | public final void testVersion36() 18 | throws PSTException, IOException, URISyntaxException { 19 | URL dirUrl = ClassLoader.getSystemResource("example-2013.ost"); 20 | PSTFile pstFile2 = new PSTFile(new File(dirUrl.toURI())); 21 | PSTFolder inbox = (PSTFolder)PSTObject.detectAndLoadPSTObject(pstFile2, 8578); 22 | Assert.assertEquals( 23 | "Number of emails in folder", 24 | inbox.getContentCount(), 25 | 2); 26 | PSTMessage msg = (PSTMessage)PSTObject.detectAndLoadPSTObject(pstFile2, 2097284); 27 | Assert.assertEquals( 28 | "correct email text.", 29 | "This is an e-mail message sent automatically by Microsoft " 30 | + "Outlook while testing the settings for your account.", 31 | msg.getBodyHTML().trim()); 32 | //processFolder(pstFile2.getRootFolder()); 33 | } 34 | 35 | int depth = -1; 36 | public void processFolder(PSTFolder folder) 37 | throws PSTException, java.io.IOException { 38 | depth++; 39 | // the root folder doesn't have a display name 40 | if (depth > 0) { 41 | printDepth(); 42 | System.out.println("Folder: " + folder.getDescriptorNodeId() + " - " + folder.getDisplayName()); 43 | } 44 | 45 | // go through the folders... 46 | if (folder.hasSubfolders()) { 47 | Vector childFolders = folder.getSubFolders(); 48 | for (PSTFolder childFolder : childFolders) { 49 | processFolder(childFolder); 50 | } 51 | } 52 | 53 | // and now the emails for this folder 54 | if (folder.getContentCount() > 0) { 55 | depth++; 56 | PSTMessage email = (PSTMessage)folder.getNextChild(); 57 | while (email != null) { 58 | printDepth(); 59 | System.out.println("Email: "+ email.getDescriptorNodeId() + " - " + email.getSubject()); 60 | email = (PSTMessage)folder.getNextChild(); 61 | } 62 | depth--; 63 | } 64 | depth--; 65 | } 66 | 67 | public void printDepth() { 68 | for (int x = 0; x < depth-1; x++) { 69 | System.out.print(" | "); 70 | } 71 | System.out.print(" |- "); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/resources/dist-list.pst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjohnsondev/java-libpst/f158a64acf2a0e46ac3bd699bc7a5a8da6c40d26/src/test/resources/dist-list.pst -------------------------------------------------------------------------------- /src/test/resources/example-2013.ost: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjohnsondev/java-libpst/f158a64acf2a0e46ac3bd699bc7a5a8da6c40d26/src/test/resources/example-2013.ost -------------------------------------------------------------------------------- /src/test/resources/passworded.pst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjohnsondev/java-libpst/f158a64acf2a0e46ac3bd699bc7a5a8da6c40d26/src/test/resources/passworded.pst --------------------------------------------------------------------------------