--- /dev/null
+
+/* ====================================================================
+ Copyright 2002-2004 Apache Software Foundation
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+
+package org.apache.poi.hslf;
+
+import java.io.FileNotFoundException;
+
+import org.apache.poi.hslf.record.CurrentUserAtom;
+import org.apache.poi.hslf.record.DocumentEncryptionAtom;
+import org.apache.poi.hslf.record.PersistPtrHolder;
+import org.apache.poi.hslf.record.Record;
+import org.apache.poi.hslf.record.UserEditAtom;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.poifs.filesystem.DocumentEntry;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * This class provides helper functions for determining if a
+ * PowerPoint document is Encrypted.
+ * In future, it may also provide Encryption and Decryption
+ * functions, but first we'd need to figure out how
+ * PowerPoint encryption is really done!
+ *
+ * @author Nick Burch
+ */
+
+public class EncryptedSlideShow
+{
+ /**
+ * Check to see if a HSLFSlideShow represents an encrypted
+ * PowerPoint document, or not
+ * @param hss The HSLFSlideShow to check
+ * @return true if encrypted, otherwise false
+ */
+ public static boolean checkIfEncrypted(HSLFSlideShow hss) {
+ // Easy way to check - contains a stream
+ // "EncryptedSummary"
+ POIFSFileSystem fs = hss.getPOIFSFileSystem();
+ try {
+ fs.getRoot().getEntry("EncryptedSummary");
+ return true;
+ } catch(FileNotFoundException fnfe) {
+ // Doesn't have encrypted properties
+ }
+
+ // If they encrypted the document but not the properties,
+ // it's harder.
+ // We need to see what the last record pointed to by the
+ // first PersistPrtHolder is - if it's a
+ // DocumentEncryptionAtom, then the file's Encrypted
+ DocumentEncryptionAtom dea = fetchDocumentEncryptionAtom(hss);
+ if(dea != null) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return the DocumentEncryptionAtom for a HSLFSlideShow, or
+ * null if there isn't one.
+ * @return a DocumentEncryptionAtom, or null if there isn't one
+ */
+ public static DocumentEncryptionAtom fetchDocumentEncryptionAtom(HSLFSlideShow hss) {
+ // Will be the last Record pointed to by the
+ // first PersistPrtHolder, if there is one
+
+ CurrentUserAtom cua = hss.getCurrentUserAtom();
+ if(cua.getCurrentEditOffset() != 0) {
+ // Grab the details of the UserEditAtom there
+ Record r = Record.buildRecordAtOffset(
+ hss.getUnderlyingBytes(),
+ (int)cua.getCurrentEditOffset()
+ );
+ if(! (r instanceof UserEditAtom)) { return null; }
+ UserEditAtom uea = (UserEditAtom)r;
+
+ // Now get the PersistPtrHolder
+ Record r2 = Record.buildRecordAtOffset(
+ hss.getUnderlyingBytes(),
+ uea.getPersistPointersOffset()
+ );
+ if(! (r2 instanceof PersistPtrHolder)) { return null; }
+ PersistPtrHolder pph = (PersistPtrHolder)r2;
+
+ // Now get the last record
+ int[] slideIds = pph.getKnownSlideIDs();
+ int maxSlideId = -1;
+ for(int i=0; i<slideIds.length; i++) {
+ if(slideIds[i] > maxSlideId) { maxSlideId = slideIds[i]; }
+ }
+ if(maxSlideId == -1) { return null; }
+
+ int offset = (
+ (Integer)pph.getSlideLocationsLookup().get(
+ new Integer(maxSlideId)
+ ) ).intValue();
+ Record r3 = Record.buildRecordAtOffset(
+ hss.getUnderlyingBytes(),
+ offset
+ );
+
+ // If we have a DocumentEncryptionAtom, it'll be this one
+ if(r3 instanceof DocumentEncryptionAtom) {
+ return (DocumentEncryptionAtom)r3;
+ }
+ }
+
+ return null;
+ }
+}
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hpsf.DocumentSummaryInformation;
+import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
import org.apache.poi.hslf.record.*;
import org.apache.poi.hslf.usermodel.PictureData;
// Raw Pictures contained in the pictures stream
private PictureData[] _pictures;
+
+ /**
+ * Returns the underlying POIFSFileSystem for the document
+ * that is open.
+ */
+ protected POIFSFileSystem getPOIFSFileSystem() {
+ return filesystem;
+ }
/**
* Constructs a Powerpoint document from fileName. Parses the document
public HSLFSlideShow(POIFSFileSystem filesystem) throws IOException
{
this.filesystem = filesystem;
+
+ // First up, grab the "Current User" stream
+ // We need this before we can detect Encrypted Documents
+ readCurrentUserStream();
+
+ // Next up, grab the data that makes up the
+ // PowerPoint stream
+ readPowerPointStream();
+
+ // Check to see if we have an encrypted document,
+ // bailing out if we do
+ boolean encrypted = EncryptedSlideShow.checkIfEncrypted(this);
+ if(encrypted) {
+ throw new EncryptedPowerPointFileException("Encrypted PowerPoint files are not supported");
+ }
- // Go find a PowerPoint document in the stream
- // Save anything useful we come across
- readFIB();
+ // Now, build records based on the PowerPoint stream
+ buildRecords();
// Look for Property Streams:
readProperties();
- // Look for other streams
+ // Look for any other streams
readOtherStreams();
// Look for Picture Streams:
}
- /**
- * Extracts the main document stream from the POI file then hands off
- * to other functions that parse other areas.
- *
- * @throws IOException
- */
- private void readFIB() throws IOException
- {
- // Get the main document stream
- DocumentEntry docProps =
- (DocumentEntry)filesystem.getRoot().getEntry("PowerPoint Document");
-
- // Grab the document stream
- _docstream = new byte[docProps.getSize()];
- filesystem.createDocumentInputStream("PowerPoint Document").read(_docstream);
-
- // The format of records in a powerpoint file are:
- // <little endian 2 byte "info">
- // <little endian 2 byte "type">
- // <little endian 4 byte "length">
- // If it has a zero length, following it will be another record
- // <xx xx yy yy 00 00 00 00> <xx xx yy yy zz zz zz zz>
- // If it has a length, depending on its type it may have children or data
- // If it has children, these will follow straight away
- // <xx xx yy yy zz zz zz zz <xx xx yy yy zz zz zz zz>>
- // If it has data, this will come straigh after, and run for the length
- // <xx xx yy yy zz zz zz zz dd dd dd dd dd dd dd>
- // All lengths given exclude the 8 byte record header
- // (Data records are known as Atoms)
-
- // Document should start with:
- // 0F 00 E8 03 ## ## ## ##
- // (type 1000 = document, info 00 0f is normal, rest is document length)
- // 01 00 E9 03 28 00 00 00
- // (type 1001 = document atom, info 00 01 normal, 28 bytes long)
- // 80 16 00 00 E0 10 00 00 xx xx xx xx xx xx xx xx
- // 05 00 00 00 0A 00 00 00 xx xx xx
- // (the contents of the document atom, not sure what it means yet)
- // (records then follow)
-
- // When parsing a document, look to see if you know about that type
- // of the current record. If you know it's a type that has children,
- // process the record's data area looking for more records
- // If you know about the type and it doesn't have children, either do
- // something with the data (eg TextRun) or skip over it
- // If you don't know about the type, play safe and skip over it (using
- // its length to know where the next record will start)
- //
- // For now, this work is handled by Record.findChildRecords
-
- _records = Record.findChildRecords(_docstream,0,_docstream.length);
- }
+ /**
+ * Extracts the main PowerPoint document stream from the
+ * POI file, ready to be passed
+ *
+ * @throws IOException
+ */
+ private void readPowerPointStream() throws IOException
+ {
+ // Get the main document stream
+ DocumentEntry docProps =
+ (DocumentEntry)filesystem.getRoot().getEntry("PowerPoint Document");
+ // Grab the document stream
+ _docstream = new byte[docProps.getSize()];
+ filesystem.createDocumentInputStream("PowerPoint Document").read(_docstream);
+ }
+
+ /**
+ * Builds the list of records, based on the contents
+ * of the PowerPoint stream
+ */
+ private void buildRecords()
+ {
+ // The format of records in a powerpoint file are:
+ // <little endian 2 byte "info">
+ // <little endian 2 byte "type">
+ // <little endian 4 byte "length">
+ // If it has a zero length, following it will be another record
+ // <xx xx yy yy 00 00 00 00> <xx xx yy yy zz zz zz zz>
+ // If it has a length, depending on its type it may have children or data
+ // If it has children, these will follow straight away
+ // <xx xx yy yy zz zz zz zz <xx xx yy yy zz zz zz zz>>
+ // If it has data, this will come straigh after, and run for the length
+ // <xx xx yy yy zz zz zz zz dd dd dd dd dd dd dd>
+ // All lengths given exclude the 8 byte record header
+ // (Data records are known as Atoms)
+
+ // Document should start with:
+ // 0F 00 E8 03 ## ## ## ##
+ // (type 1000 = document, info 00 0f is normal, rest is document length)
+ // 01 00 E9 03 28 00 00 00
+ // (type 1001 = document atom, info 00 01 normal, 28 bytes long)
+ // 80 16 00 00 E0 10 00 00 xx xx xx xx xx xx xx xx
+ // 05 00 00 00 0A 00 00 00 xx xx xx
+ // (the contents of the document atom, not sure what it means yet)
+ // (records then follow)
+
+ // When parsing a document, look to see if you know about that type
+ // of the current record. If you know it's a type that has children,
+ // process the record's data area looking for more records
+ // If you know about the type and it doesn't have children, either do
+ // something with the data (eg TextRun) or skip over it
+ // If you don't know about the type, play safe and skip over it (using
+ // its length to know where the next record will start)
+ //
+ // For now, this work is handled by Record.findChildRecords
+
+ _records = Record.findChildRecords(_docstream,0,_docstream.length);
+ }
/**
- * Find the other from the filesystem (currently just CurrentUserAtom),
- * and load them
+ * Find the "Current User" stream, and load it
*/
- public void readOtherStreams() {
- // Current User
+ private void readCurrentUserStream() {
try {
currentUser = new CurrentUserAtom(filesystem);
} catch(IOException ie) {
currentUser = new CurrentUserAtom();
}
}
+
+ /**
+ * Find any other streams from the filesystem, and load them
+ */
+ private void readOtherStreams() {
+ // Currently, there aren't any
+ }
/**
* Find and read in pictures contained in this presentation
--- /dev/null
+
+/* ====================================================================
+ Copyright 2002-2004 Apache Software Foundation
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+
+package org.apache.poi.hslf.exceptions;
+
+/**
+ * This exception is thrown when we try to open a PowerPoint file, and
+ * discover that it is encrypted
+ *
+ * @author Nick Burch
+ */
+
+public class EncryptedPowerPointFileException extends IllegalStateException
+{
+ public EncryptedPowerPointFileException(String s) {
+ super(s);
+ }
+}
--- /dev/null
+
+/* ====================================================================
+ Copyright 2002-2004 Apache Software Foundation
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hslf.record;
+
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.StringUtil;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * A Document Encryption Atom (type 12052). Holds information
+ * on the Encryption of a Document
+ *
+ * @author Nick Burch
+ */
+
+public class DocumentEncryptionAtom extends RecordAtom
+{
+ private byte[] _header;
+ private static long _type = 12052l;
+
+ private byte[] data;
+ private String encryptionProviderName;
+
+ /**
+ * For the Document Encryption Atom
+ */
+ protected DocumentEncryptionAtom(byte[] source, int start, int len) {
+ // Get the header
+ _header = new byte[8];
+ System.arraycopy(source,start,_header,0,8);
+
+ // Grab everything else, for now
+ data = new byte[len-8];
+ System.arraycopy(source, start+8, data, 0, len-8);
+
+ // Grab the provider, from byte 8+44 onwards
+ // It's a null terminated Little Endian String
+ int endPos = -1;
+ int pos = start + 8+44;
+ while(pos < (start+len) && endPos < 0) {
+ if(source[pos] == 0 && source[pos+1] == 0) {
+ // Hit the end
+ endPos = pos;
+ }
+ pos += 2;
+ }
+ pos = start + 8+44;
+ int stringLen = (endPos-pos) / 2;
+ encryptionProviderName = StringUtil.getFromUnicodeLE(source, pos, stringLen);
+ }
+
+ /**
+ * Return the length of the encryption key, in bits
+ */
+ public int getKeyLength() {
+ return data[28];
+ }
+
+ /**
+ * Return the name of the encryption provider used
+ */
+ public String getEncryptionProviderName() {
+ return encryptionProviderName;
+ }
+
+
+ /**
+ * We are of type 12052
+ */
+ public long getRecordType() { return _type; }
+
+ /**
+ * Write the contents of the record back, so it can be written
+ * to disk
+ */
+ public void writeOut(OutputStream out) throws IOException {
+ // Header
+ out.write(_header);
+
+ // Data
+ out.write(data);
+ }
+}
LittleEndian.putShort(bs,s);
o.write(bs);
}
+
+ /**
+ * Build and return the Record at the given offset.
+ * Note - does less error checking and handling than findChildRecords
+ * @param b The byte array to build from
+ * @param offset The offset to build at
+ */
+ public static Record buildRecordAtOffset(byte[] b, int offset) {
+ long type = LittleEndian.getUShort(b,offset+2);
+ long rlen = LittleEndian.getUInt(b,offset+4);
+
+ // Sanity check the length
+ int rleni = (int)rlen;
+ if(rleni < 0) { rleni = 0; }
+
+ return createRecordForType(type,b,offset,8+rleni);
+ }
/**
* Default method for finding child records of a container record
public static final Type Comment2000Atom = new Type(12001,Comment2000Atom.class);
public static final Type Comment2000Summary = new Type(12004,null);
public static final Type Comment2000SummaryAtom = new Type(12005,null);
+
+ // Records ~12050 seem to be related to Document Encryption
+ public static final Type DocumentEncryptionAtom = new Type(12052,DocumentEncryptionAtom.class);
//records greater then 0xF000 belong to with Microsoft Office Drawing format also known as Escher
public static final int EscherDggContainer = 0xf000;
--- /dev/null
+
+/* ====================================================================
+ Copyright 2002-2004 Apache Software Foundation
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+
+package org.apache.poi.hslf;
+
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
+import org.apache.poi.hslf.record.*;
+
+/**
+ * Tests that HSLFSlideShow does the right thing with an encrypted file
+ *
+ * @author Nick Burch (nick at torchbox dot com)
+ */
+public class TestEncryptedFile extends TestCase {
+ // A non encrypted file
+ private String ss_ne;
+ // An encrypted file, with encrypted properties
+ private String ss_e;
+ // An encrypted file, without encrypted properties
+ private String ss_np_e;
+ // An encrypted file, with a 56 bit key
+ private String ss_56_e;
+
+
+ public TestEncryptedFile() throws Exception {
+ String dirname = System.getProperty("HSLF.testdata.path");
+
+ ss_ne = dirname + "/basic_test_ppt_file.ppt";
+ ss_e = dirname + "/Password_Protected-hello.ppt";
+ ss_np_e = dirname + "/Password_Protected-np-hello.ppt";
+ ss_56_e = dirname + "/Password_Protected-56-hello.ppt";
+ }
+
+ public void testLoadNonEncrypted() throws Exception {
+ HSLFSlideShow hss = new HSLFSlideShow(ss_ne);
+
+ assertNotNull(hss);
+ }
+
+ public void testLoadEncrypted() throws Exception {
+ try {
+ new HSLFSlideShow(ss_e);
+ fail();
+ } catch(EncryptedPowerPointFileException e) {
+ // Good
+ }
+
+ try {
+ new HSLFSlideShow(ss_np_e);
+ fail();
+ } catch(EncryptedPowerPointFileException e) {
+ // Good
+ }
+
+ try {
+ new HSLFSlideShow(ss_56_e);
+ fail();
+ } catch(EncryptedPowerPointFileException e) {
+ // Good
+ }
+ }
+}
--- /dev/null
+
+/* ====================================================================
+ Copyright 2002-2004 Apache Software Foundation
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+
+package org.apache.poi.hslf.record;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests that DocumentEncryptionAtom works properly.
+ *
+ * @author Nick Burch (nick at torchbox dot com)
+ */
+public class TestDocumentEncryptionAtom extends TestCase {
+ // From a real file
+ private byte[] data_a = new byte[] {
+ 0x0F, 00, 0x14, 0x2F, 0xBE-256, 00, 00, 00,
+ 02, 00, 02, 00, 0x0C, 00, 00, 00,
+ 0x76, 00, 00, 00, 0x0C, 00, 00, 00,
+ 00, 00, 00, 00, 01, 0x68, 00, 00,
+ 04, 0x80-256, 00, 00, 0x28, 00, 00, 00,
+ 01, 00, 00, 00, 0x30, 00, 0x26, 01,
+ 00, 00, 00, 00,
+
+ 0x4D, 00, 0x69, 00,
+ 0x63, 00, 0x72, 00, 0x6F, 00, 0x73, 00,
+ 0x6F, 00, 0x66, 00, 0x74, 00, 0x20, 00,
+ 0x42, 00, 0x61, 00, 0x73, 00, 0x65, 00,
+ 0x20, 00, 0x43, 00, 0x72, 00, 0x79, 00,
+ 0x70, 00, 0x74, 00, 0x6F, 00, 0x67, 00,
+ 0x72, 00, 0x61, 00, 0x70, 00, 0x68, 00,
+ 0x69, 00, 0x63, 00, 0x20, 00, 0x50, 00,
+ 0x72, 00, 0x6F, 00, 0x76, 00, 0x69, 00,
+ 0x64, 00, 0x65, 00, 0x72, 00, 0x20, 00,
+ 0x76, 00, 0x31, 00, 0x2E, 00, 0x30, 00,
+ 0x00, 0x00,
+
+ 0x10, 00, 0x00, 00,
+ 0x62, 0xA6-256,
+ 0xDF-256, 0xEA-256, 0x96-256, 0x84-256,
+ 0xFB-256, 0x89-256, 0x93-256, 0xCA-256,
+ 0xBA-256, 0xEE-256, 0x8E-256, 0x43,
+ 0xC8-256, 0x71, 0xD1-256, 0x89-256,
+ 0xF6-256, 0x4B, 0x2B, 0xD9-256,
+ 0x7E, 0x0B, 0x52, 0xFB-256,
+ 0x68, 0xD7-256, 0x5A, 0x4E, 0x45, 0xDF-256, 0x14, 0x00,
+ 0x00, 0x00, 0x93-256, 0x15, 0x27, 0xEB-256, 0x21, 0x54,
+ 0x7F, 0x0B, 0x56, 0x07, 0xEE-256, 0x66, 0xEB-256, 0x6F,
+ 0xB2-256, 0x8E-256, 0x67, 0x54, 0x07, 0x04, 0x00
+ };
+
+ private byte[] data_b = new byte[] {
+ 15, 0, 20, 47, -66, 0, 0, 0,
+ 2, 0, 2, 0, 4,
+ 0, 0, 0, 118, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0,
+ 0, 1, 104, 0, 0, 4, -128, 0, 0, 56, 0, 0, 0,
+ 1, 0, 0, 0, 48, 0, 38, 1, 0, 0, 0, 0, 77, 0,
+ 105, 0, 99, 0, 114, 0, 111, 0, 115, 0, 111,
+ 0, 102, 0, 116, 0, 32, 0, 66, 0, 97, 0, 115,
+ 0, 101, 0, 32, 0, 67, 0, 114, 0, 121, 0, 112,
+ 0, 116, 0, 111, 0, 103, 0, 114, 0, 97, 0,
+ 112, 0, 104, 0, 105, 0, 99, 0, 32, 0, 80, 0,
+ 114, 0, 111, 0, 118, 0, 105, 0, 100, 0, 101,
+ 0, 114, 0, 32, 0, 118, 0, 49, 0, 46, 0, 48,
+ 0, 0, 0, 16, 0, 0, 0, -80, -66, 112, -40, 57,
+ 110, 54, 80, 64, 61, -73, -29, 48, -35, -20,
+ 17, -40, 84, 54, 6, -103, 125, -22, -72, 53,
+ 103, -114, 13, -48, 111, 29, 78, 20, 0, 0,
+ 0, -97, -67, 55, -62, -94, 14, 15, -21, 37,
+ 3, -104, 22, 6, 102, -61, -98, 62, 40, 61, 21
+ };
+
+ public void testRecordType() throws Exception {
+ DocumentEncryptionAtom dea1 = new DocumentEncryptionAtom(data_a, 0, data_a.length);
+ assertEquals(12052l, dea1.getRecordType());
+
+ DocumentEncryptionAtom dea2 = new DocumentEncryptionAtom(data_b, 0, data_b.length);
+ assertEquals(12052l, dea2.getRecordType());
+
+ System.out.println(data_a.length);
+ System.out.println(data_b.length);
+ }
+
+ public void testEncryptionTypeName() throws Exception {
+ DocumentEncryptionAtom dea1 = new DocumentEncryptionAtom(data_a, 0, data_a.length);
+ assertEquals("Microsoft Base Cryptographic Provider v1.0", dea1.getEncryptionProviderName());
+
+ DocumentEncryptionAtom dea2 = new DocumentEncryptionAtom(data_b, 0, data_b.length);
+ assertEquals("Microsoft Base Cryptographic Provider v1.0", dea2.getEncryptionProviderName());
+ }
+}