poi-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kiwiwi...@apache.org
Subject svn commit: r1647867 [3/4] - in /poi: site/src/documentation/content/xdocs/ trunk/src/java/org/apache/poi/ trunk/src/java/org/apache/poi/poifs/crypt/ trunk/src/java/org/apache/poi/poifs/crypt/binaryrc4/ trunk/src/java/org/apache/poi/poifs/crypt/cryptoa...
Date Thu, 25 Dec 2014 01:56:30 GMT
Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hslf/EncryptedSlideShow.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hslf/EncryptedSlideShow.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hslf/EncryptedSlideShow.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hslf/EncryptedSlideShow.java Thu Dec 25 01:56:29 2014
@@ -17,117 +17,478 @@
 
 package org.apache.poi.hslf;
 
-import java.io.FileNotFoundException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
 
 import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
-import org.apache.poi.hslf.record.CurrentUserAtom;
+import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
 import org.apache.poi.hslf.record.DocumentEncryptionAtom;
 import org.apache.poi.hslf.record.PersistPtrHolder;
+import org.apache.poi.hslf.record.PositionDependentRecord;
 import org.apache.poi.hslf.record.Record;
 import org.apache.poi.hslf.record.UserEditAtom;
+import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
+import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.Encryptor;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.Internal;
+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
+ * This class provides helper functions for encrypted PowerPoint documents.
  */
+@Internal
+public class EncryptedSlideShow {
+    DocumentEncryptionAtom dea;
+    CryptoAPIEncryptor enc = null;
+    CryptoAPIDecryptor dec = null;
+    Cipher cipher = null;
+    CipherOutputStream cyos = null;
+
+    private static final BitField fieldRecInst = new BitField(0xFFF0);
+    
+    protected EncryptedSlideShow(DocumentEncryptionAtom dea) {
+        this.dea = dea;
+    }
+
+    protected EncryptedSlideShow(byte[] docstream, NavigableMap<Integer,Record> recordMap) {
+        // check for DocumentEncryptionAtom, which would be at the last offset
+        // need to ignore already set UserEdit and PersistAtoms
+        UserEditAtom userEditAtomWithEncryption = null;
+        for (Map.Entry<Integer, Record> me : recordMap.descendingMap().entrySet()) {
+            Record r = me.getValue();
+            if (!(r instanceof UserEditAtom)) continue;
+            UserEditAtom uea = (UserEditAtom)r;
+            if (uea.getEncryptSessionPersistIdRef() != -1) {
+                userEditAtomWithEncryption = uea;
+                break;
+            }
+        }
+
+        if (userEditAtomWithEncryption == null) {
+            dea = null;
+            return;
+        }
+
+        Record r = recordMap.get(userEditAtomWithEncryption.getPersistPointersOffset());
+        assert(r instanceof PersistPtrHolder);
+        PersistPtrHolder ptr = (PersistPtrHolder)r;
+        
+        Integer encOffset = ptr.getSlideLocationsLookup().get(userEditAtomWithEncryption.getEncryptSessionPersistIdRef());
+        assert(encOffset != null);
+        
+        r = recordMap.get(encOffset);
+        if (r == null) {
+            r = Record.buildRecordAtOffset(docstream, encOffset);
+            recordMap.put(encOffset, r);
+        }
+        assert(r instanceof DocumentEncryptionAtom);
+        this.dea = (DocumentEncryptionAtom)r;
+        
+        CryptoAPIDecryptor dec = (CryptoAPIDecryptor)dea.getEncryptionInfo().getDecryptor();
+        String pass = Biff8EncryptionKey.getCurrentUserPassword();
+        if(!dec.verifyPassword(pass != null ? pass : Decryptor.DEFAULT_PASSWORD)) {
+            throw new EncryptedPowerPointFileException("PowerPoint file is encrypted. The correct password needs to be set via Biff8EncryptionKey.setCurrentUserPassword()");
+        }
+     }
+
+    public DocumentEncryptionAtom getDocumentEncryptionAtom() {
+        return dea;
+    }
+    
+    protected void setPersistId(int persistId) {
+        if (enc != null && dec != null) {
+            throw new EncryptedPowerPointFileException("Use instance either for en- or decryption");
+        }
+        
+        try {
+            if (enc != null) cipher = enc.initCipherForBlock(cipher, persistId);
+            if (dec != null) cipher = dec.initCipherForBlock(cipher, persistId);
+        } catch (GeneralSecurityException e) {
+            throw new EncryptedPowerPointFileException(e);
+        }
+    }
+    
+    protected void decryptInit() {
+        if (dec != null) return;
+        EncryptionInfo ei = dea.getEncryptionInfo();
+        dec = (CryptoAPIDecryptor)ei.getDecryptor();
+    }
+    
+    protected void encryptInit() {
+        if (enc != null) return;
+        EncryptionInfo ei = dea.getEncryptionInfo();
+        enc = (CryptoAPIEncryptor)ei.getEncryptor();
+    }
+    
+
+    
+    protected OutputStream encryptRecord(OutputStream plainStream, int persistId, Record record) {
+        boolean isPlain = (dea == null 
+            || record instanceof UserEditAtom
+            || record instanceof PersistPtrHolder
+            || record instanceof DocumentEncryptionAtom
+        );
+        if (isPlain) return plainStream;
+
+        encryptInit();
+        setPersistId(persistId);
+        
+        if (cyos == null) {
+            cyos = new CipherOutputStream(plainStream, cipher);
+        }
+        return cyos;
+    }
+
+    protected void decryptRecord(byte[] docstream, int persistId, int offset) {
+        if (dea == null) return;
+
+        decryptInit();
+        setPersistId(persistId);
+        
+        try {
+            // decrypt header and read length to be decrypted
+            cipher.update(docstream, offset, 8, docstream, offset);
+            // decrypt the rest of the record
+            int rlen = (int)LittleEndian.getUInt(docstream, offset+4);
+            cipher.update(docstream, offset+8, rlen, docstream, offset+8);
+        } catch (GeneralSecurityException e) {
+            throw new CorruptPowerPointFileException(e);
+        }       
+    }        
+    
+    protected void decryptPicture(byte[] pictstream, int offset) {
+        if (dea == null) return;
+        
+        decryptInit();
+        setPersistId(0);
+        
+        try {
+            // decrypt header and read length to be decrypted
+            cipher.doFinal(pictstream, offset, 8, pictstream, offset);
+            int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
+            int recType = LittleEndian.getUShort(pictstream, offset+2);
+            int rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
+            offset += 8;
+            int endOffset = offset + rlen; 
+
+            if (recType == 0xF007) {
+                // TOOD: get a real example file ... to actual test the FBSE entry
+                // not sure where the foDelay block is
+                
+                // File BLIP Store Entry (FBSE)
+                cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btWin32
+                offset++;
+                cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btMacOS
+                offset++;
+                cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid
+                offset += 16;
+                cipher.doFinal(pictstream, offset, 2, pictstream, offset); // tag
+                offset += 2;
+                cipher.doFinal(pictstream, offset, 4, pictstream, offset); // size
+                offset += 4;
+                cipher.doFinal(pictstream, offset, 4, pictstream, offset); // cRef
+                offset += 4;
+                cipher.doFinal(pictstream, offset, 4, pictstream, offset); // foDelay
+                offset += 4;
+                cipher.doFinal(pictstream, offset+0, 1, pictstream, offset+0); // unused1
+                cipher.doFinal(pictstream, offset+1, 1, pictstream, offset+1); // cbName
+                cipher.doFinal(pictstream, offset+2, 1, pictstream, offset+2); // unused2
+                cipher.doFinal(pictstream, offset+3, 1, pictstream, offset+3); // unused3
+                int cbName = LittleEndian.getUShort(pictstream, offset+1);
+                offset += 4;
+                if (cbName > 0) {
+                    cipher.doFinal(pictstream, offset, cbName, pictstream, offset); // nameData
+                    offset += cbName;
+                }
+                if (offset == endOffset) {
+                    return; // no embedded blip
+                }
+                // fall through, read embedded blip now
+
+                // update header data
+                cipher.doFinal(pictstream, offset, 8, pictstream, offset);
+                recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
+                recType = LittleEndian.getUShort(pictstream, offset+2);
+                rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
+                offset += 8;
+            }
+
+            int rgbUidCnt = (recInst == 0x217 || recInst == 0x3D5 || recInst == 0x46B || recInst == 0x543 ||
+                recInst == 0x6E1 || recInst == 0x6E3 || recInst == 0x6E5 || recInst == 0x7A9) ? 2 : 1;
+            
+            for (int i=0; i<rgbUidCnt; i++) {
+                cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid 1/2
+                offset += 16;
+            }
+            
+            if (recType == 0xF01A || recType == 0XF01B || recType == 0XF01C) {
+                cipher.doFinal(pictstream, offset, 34, pictstream, offset); // metafileHeader
+                offset += 34;
+            } else {
+                cipher.doFinal(pictstream, offset, 1, pictstream, offset); // tag
+                offset += 1;
+            }
+            
+            int blipLen = endOffset - offset;
+            cipher.doFinal(pictstream, offset, blipLen, pictstream, offset);
+        } catch (GeneralSecurityException e) {
+            throw new CorruptPowerPointFileException(e);
+        }       
+    }
+
+    protected void encryptPicture(byte[] pictstream, int offset) {
+        if (dea == null) return;
+        
+        encryptInit();
+        setPersistId(0);
+
+        try {
+            int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
+            int recType = LittleEndian.getUShort(pictstream, offset+2);
+            int rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
+            cipher.doFinal(pictstream, offset, 8, pictstream, offset);
+            offset += 8;
+            int endOffset = offset + rlen; 
+
+            if (recType == 0xF007) {
+                // TOOD: get a real example file ... to actual test the FBSE entry
+                // not sure where the foDelay block is
+                
+                // File BLIP Store Entry (FBSE)
+                cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btWin32
+                offset++;
+                cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btMacOS
+                offset++;
+                cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid
+                offset += 16;
+                cipher.doFinal(pictstream, offset, 2, pictstream, offset); // tag
+                offset += 2;
+                cipher.doFinal(pictstream, offset, 4, pictstream, offset); // size
+                offset += 4;
+                cipher.doFinal(pictstream, offset, 4, pictstream, offset); // cRef
+                offset += 4;
+                cipher.doFinal(pictstream, offset, 4, pictstream, offset); // foDelay
+                offset += 4;
+                int cbName = LittleEndian.getUShort(pictstream, offset+1);
+                cipher.doFinal(pictstream, offset+0, 1, pictstream, offset+0); // unused1
+                cipher.doFinal(pictstream, offset+1, 1, pictstream, offset+1); // cbName
+                cipher.doFinal(pictstream, offset+2, 1, pictstream, offset+2); // unused2
+                cipher.doFinal(pictstream, offset+3, 1, pictstream, offset+3); // unused3
+                offset += 4;
+                if (cbName > 0) {
+                    cipher.doFinal(pictstream, offset, cbName, pictstream, offset); // nameData
+                    offset += cbName;
+                }
+                if (offset == endOffset) {
+                    return; // no embedded blip
+                }
+                // fall through, read embedded blip now
+
+                // update header data
+                recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
+                recType = LittleEndian.getUShort(pictstream, offset+2);
+                rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
+                cipher.doFinal(pictstream, offset, 8, pictstream, offset);
+                offset += 8;
+            }
+            
+            int rgbUidCnt = (recInst == 0x217 || recInst == 0x3D5 || recInst == 0x46B || recInst == 0x543 ||
+                recInst == 0x6E1 || recInst == 0x6E3 || recInst == 0x6E5 || recInst == 0x7A9) ? 2 : 1;
+                
+            for (int i=0; i<rgbUidCnt; i++) {
+                cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid 1/2
+                offset += 16;
+            }
+            
+            if (recType == 0xF01A || recType == 0XF01B || recType == 0XF01C) {
+                cipher.doFinal(pictstream, offset, 34, pictstream, offset); // metafileHeader
+                offset += 34;
+            } else {
+                cipher.doFinal(pictstream, offset, 1, pictstream, offset); // tag
+                offset += 1;
+            }
+            
+            int blipLen = endOffset - offset;
+            cipher.doFinal(pictstream, offset, blipLen, pictstream, offset);
+        } catch (GeneralSecurityException e) {
+            throw new CorruptPowerPointFileException(e);
+        }       
+    }
+
+    protected Record[] updateEncryptionRecord(Record records[]) {
+        String password = Biff8EncryptionKey.getCurrentUserPassword();
+        if (password == null) {
+            if (dea == null) {
+                // no password given, no encryption record exits -> done
+                return records;
+            } else {
+                // need to remove password data
+                dea = null;
+                return removeEncryptionRecord(records);
+            }
+        } else {
+            // create password record
+            if (dea == null) {
+                dea = new DocumentEncryptionAtom();
+            }
+            EncryptionInfo ei = dea.getEncryptionInfo();
+            byte salt[] = ei.getVerifier().getSalt();
+            Encryptor enc = ei.getEncryptor();
+            if (salt == null) {
+                enc.confirmPassword(password);
+            } else {
+                byte verifier[] = ei.getDecryptor().getVerifier();
+                enc.confirmPassword(password, null, null, verifier, salt, null);
+            }
+
+            // move EncryptionRecord to last slide position
+            records = normalizeRecords(records);
+            return addEncryptionRecord(records, dea);
+        }
+    }
+
+    /**
+     * remove duplicated UserEditAtoms and merge PersistPtrHolder.
+     * Before this method is called, make sure that the offsets are correct,
+     * i.e. call {@link HSLFSlideShow#updateAndWriteDependantRecords(OutputStream, Map)}
+     */
+    protected static Record[] normalizeRecords(Record records[]) {
+        // http://msdn.microsoft.com/en-us/library/office/gg615594(v=office.14).aspx
+        // repeated slideIds can be overwritten, i.e. ignored
+        
+        UserEditAtom uea = null;
+        PersistPtrHolder pph = null;
+        TreeMap<Integer,Integer> slideLocations = new TreeMap<Integer,Integer>();
+        TreeMap<Integer,Record> recordMap = new TreeMap<Integer,Record>();
+        List<Integer> obsoleteOffsets = new ArrayList<Integer>();
+        int duplicatedCount = 0;
+        for (Record r : records) {
+            assert(r instanceof PositionDependentRecord);
+            PositionDependentRecord pdr = (PositionDependentRecord)r;
+            if (pdr instanceof UserEditAtom) {
+                uea = (UserEditAtom)pdr;
+                continue;
+            }
+            
+            if (pdr instanceof PersistPtrHolder) {
+                if (pph != null) {
+                    duplicatedCount++;
+                }
+                pph = (PersistPtrHolder)pdr;
+                for (Map.Entry<Integer,Integer> me : pph.getSlideLocationsLookup().entrySet()) {
+                    Integer oldOffset = slideLocations.put(me.getKey(), me.getValue());
+                    if (oldOffset != null) obsoleteOffsets.add(oldOffset);
+                }
+                continue;
+            }
+            
+            recordMap.put(pdr.getLastOnDiskOffset(), r);
+        }
+        recordMap.put(pph.getLastOnDiskOffset(), pph);
+        recordMap.put(uea.getLastOnDiskOffset(), uea);
+
+        assert(uea != null && pph != null && uea.getPersistPointersOffset() == pph.getLastOnDiskOffset());
+        
+        if (duplicatedCount == 0 && obsoleteOffsets.isEmpty()) {
+            return records;
+        }
+
+        uea.setLastUserEditAtomOffset(0);
+        pph.clear();
+        for (Map.Entry<Integer,Integer> me : slideLocations.entrySet()) {
+            pph.addSlideLookup(me.getKey(), me.getValue());
+        }
+        
+        for (Integer oldOffset : obsoleteOffsets) {
+            recordMap.remove(oldOffset);
+        }
+        
+        return recordMap.values().toArray(new Record[recordMap.size()]);
+    }
+     
+    
+    protected static Record[] removeEncryptionRecord(Record records[]) {
+        int deaSlideId = -1;
+        int deaOffset = -1;
+        PersistPtrHolder ptr = null;
+        UserEditAtom uea = null;
+        List<Record> recordList = new ArrayList<Record>();
+        for (Record r : records) {
+            if (r instanceof DocumentEncryptionAtom) {
+                deaOffset = ((DocumentEncryptionAtom)r).getLastOnDiskOffset();
+                continue;
+            } else if (r instanceof UserEditAtom) {
+                uea = (UserEditAtom)r;
+                deaSlideId = uea.getEncryptSessionPersistIdRef();
+                uea.setEncryptSessionPersistIdRef(-1);
+            } else if (r instanceof PersistPtrHolder) {
+                ptr = (PersistPtrHolder)r;
+            }
+            recordList.add(r);
+        }
+        
+        assert(ptr != null);
+        if (deaSlideId == -1 && deaOffset == -1) return records;
+        
+        TreeMap<Integer,Integer> tm = new TreeMap<Integer,Integer>(ptr.getSlideLocationsLookup());
+        ptr.clear();
+        int maxSlideId = -1;
+        for (Map.Entry<Integer,Integer> me : tm.entrySet()) {
+            if (me.getKey() == deaSlideId || me.getValue() == deaOffset) continue;
+            ptr.addSlideLookup(me.getKey(), me.getValue());
+            maxSlideId = Math.max(me.getKey(), maxSlideId);
+        }
+        
+        uea.setMaxPersistWritten(maxSlideId);
+
+        records = recordList.toArray(new Record[recordList.size()]);
+        
+        return records;
+    }
+
+
+    protected static Record[] addEncryptionRecord(Record records[], DocumentEncryptionAtom dea) {
+        assert(dea != null);
+        int ueaIdx = -1, ptrIdx = -1, deaIdx = -1, idx = -1;
+        for (Record r : records) {
+            idx++;
+            if (r instanceof UserEditAtom) ueaIdx = idx;
+            else if (r instanceof PersistPtrHolder) ptrIdx = idx;
+            else if (r instanceof DocumentEncryptionAtom) deaIdx = idx;
+        }
+        assert(ueaIdx != -1 && ptrIdx != -1 && ptrIdx < ueaIdx);
+        if (deaIdx != -1) {
+            DocumentEncryptionAtom deaOld = (DocumentEncryptionAtom)records[deaIdx];
+            dea.setLastOnDiskOffset(deaOld.getLastOnDiskOffset());
+            records[deaIdx] = dea;
+            return records;
+        } else {
+            PersistPtrHolder ptr = (PersistPtrHolder)records[ptrIdx];
+            UserEditAtom uea = ((UserEditAtom)records[ueaIdx]);
+            dea.setLastOnDiskOffset(ptr.getLastOnDiskOffset()-1);
+            int nextSlideId = uea.getMaxPersistWritten()+1;
+            ptr.addSlideLookup(nextSlideId, ptr.getLastOnDiskOffset()-1);
+            uea.setEncryptSessionPersistIdRef(nextSlideId);
+            uea.setMaxPersistWritten(nextSlideId);
+            
+            Record newRecords[] = new Record[records.length+1];
+            if (ptrIdx > 0) System.arraycopy(records, 0, newRecords, 0, ptrIdx);
+            if (ptrIdx < records.length-1) System.arraycopy(records, ptrIdx, newRecords, ptrIdx+1, records.length-ptrIdx);
+            newRecords[ptrIdx] = dea;
+            return newRecords;
+        }
+    }
 
-public final 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"
-      try {
-         hss.getPOIFSDirectory().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) {
-			// Check it's not past the end of the file
-			if(cua.getCurrentEditOffset() > hss.getUnderlyingBytes().length) {
-				throw new CorruptPowerPointFileException("The CurrentUserAtom claims that the offset of last edit details are past the end of the file");
-			}
-
-			// Grab the details of the UserEditAtom there
-			// If the record's messed up, we could AIOOB
-			Record r = null;
-			try {
-				r = Record.buildRecordAtOffset(
-						hss.getUnderlyingBytes(),
-						(int)cua.getCurrentEditOffset()
-				);
-			} catch (ArrayIndexOutOfBoundsException e) {
-				return null;
-			}
-			if(r == null) { return null; }
-			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(
-							Integer.valueOf(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;
-	}
 }

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java Thu Dec 25 01:56:29 2014
@@ -23,6 +23,7 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Hashtable;
@@ -32,10 +33,11 @@ import java.util.NavigableMap;
 import java.util.TreeMap;
 
 import org.apache.poi.POIDocument;
+import org.apache.poi.hpsf.PropertySet;
 import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
-import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
 import org.apache.poi.hslf.exceptions.HSLFException;
 import org.apache.poi.hslf.record.CurrentUserAtom;
+import org.apache.poi.hslf.record.DocumentEncryptionAtom;
 import org.apache.poi.hslf.record.ExOleObjStg;
 import org.apache.poi.hslf.record.PersistPtrHolder;
 import org.apache.poi.hslf.record.PersistRecord;
@@ -45,6 +47,7 @@ import org.apache.poi.hslf.record.Record
 import org.apache.poi.hslf.record.UserEditAtom;
 import org.apache.poi.hslf.usermodel.ObjectData;
 import org.apache.poi.hslf.usermodel.PictureData;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor;
 import org.apache.poi.poifs.filesystem.DirectoryNode;
 import org.apache.poi.poifs.filesystem.DocumentEntry;
 import org.apache.poi.poifs.filesystem.DocumentInputStream;
@@ -182,13 +185,6 @@ public final class HSLFSlideShow extends
 		//  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");
-		}
-
 		// Now, build records based on the PowerPoint stream
 		buildRecords();
 
@@ -278,6 +274,7 @@ public final class HSLFSlideShow extends
 	    NavigableMap<Integer,Record> records = new TreeMap<Integer,Record>(); // offset -> record
         Map<Integer,Integer> persistIds = new HashMap<Integer,Integer>(); // offset -> persistId
         initRecordOffsets(docstream, usrOffset, records, persistIds);
+        EncryptedSlideShow decryptData = new EncryptedSlideShow(docstream, records);
         
         for (Map.Entry<Integer,Record> entry : records.entrySet()) {
             Integer offset = entry.getKey();
@@ -286,6 +283,7 @@ public final class HSLFSlideShow extends
             if (record == null) {
                 // all plain records have been already added,
                 // only new records need to be decrypted (tbd #35897)
+                decryptData.decryptRecord(docstream, persistId, offset);
                 record = Record.buildRecordAtOffset(docstream, offset);
                 entry.setValue(record);
             }
@@ -335,6 +333,16 @@ public final class HSLFSlideShow extends
         }       
     }
 
+    public DocumentEncryptionAtom getDocumentEncryptionAtom() {
+        for (Record r : _records) {
+            if (r instanceof DocumentEncryptionAtom) {
+                return (DocumentEncryptionAtom)r;
+            }
+        }
+        return null;
+    }
+    
+    
 	/**
 	 * Find the "Current User" stream, and load it
 	 */
@@ -353,6 +361,7 @@ public final class HSLFSlideShow extends
 	private void readOtherStreams() {
 		// Currently, there aren't any
 	}
+	
 	/**
 	 * Find and read in pictures contained in this presentation.
 	 * This is lazily called as and when we want to touch pictures.
@@ -363,6 +372,8 @@ public final class HSLFSlideShow extends
 
         // if the presentation doesn't contain pictures - will use a null set instead
         if (!directory.hasEntry("Pictures")) return;
+
+        EncryptedSlideShow decryptData = new EncryptedSlideShow(getDocumentEncryptionAtom());
         
 		DocumentEntry entry = (DocumentEntry)directory.getEntry("Pictures");
 		byte[] pictstream = new byte[entry.getSize()];
@@ -375,6 +386,8 @@ public final class HSLFSlideShow extends
 		// An empty picture record (length 0) will take up 8 bytes
         while (pos <= (pictstream.length-8)) {
             int offset = pos;
+
+            decryptData.decryptPicture(pictstream, offset);
             
             // Image signature
             int signature = LittleEndian.getUShort(pictstream, pos);
@@ -422,7 +435,21 @@ public final class HSLFSlideShow extends
             pos += imgsize;
         }
 	}
-
+    
+    /**
+     * remove duplicated UserEditAtoms and merge PersistPtrHolder, i.e.
+     * remove document edit history
+     */
+    public void normalizeRecords() {
+        try {
+            updateAndWriteDependantRecords(null, null);
+        } catch (IOException e) {
+            throw new CorruptPowerPointFileException(e);
+        }
+        _records = EncryptedSlideShow.normalizeRecords(_records);
+    }
+   
+    
 	/**
      * This is a helper functions, which is needed for adding new position dependent records
      * or finally write the slideshow to a file.
@@ -444,55 +471,67 @@ public final class HSLFSlideShow extends
         //   records are going to end up, in the new scheme
         // (Annoyingly, some powerpoint files have PersistPtrHolders
         //  that reference slides after the PersistPtrHolder)
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        UserEditAtom usr = null;
+        PersistPtrHolder ptr = null;
+        CountingOS cos = new CountingOS();
         for (Record record : _records) {
-            if(record instanceof PositionDependentRecord) {
-                PositionDependentRecord pdr = (PositionDependentRecord)record;
-                int oldPos = pdr.getLastOnDiskOffset();
-                int newPos = baos.size();
-                pdr.setLastOnDiskOffset(newPos);
-                if (oldPos != UNSET_OFFSET) {
-                    // new records don't need a mapping, as they aren't in a relation yet
-                    oldToNewPositions.put(Integer.valueOf(oldPos),Integer.valueOf(newPos));
-                }
+            // all top level records are position dependent
+            assert(record instanceof PositionDependentRecord);
+            PositionDependentRecord pdr = (PositionDependentRecord)record;
+            int oldPos = pdr.getLastOnDiskOffset();
+            int newPos = cos.size();
+            pdr.setLastOnDiskOffset(newPos);
+            if (oldPos != UNSET_OFFSET) {
+                // new records don't need a mapping, as they aren't in a relation yet
+                oldToNewPositions.put(oldPos,newPos);
+            }
+
+            // Grab interesting records as they come past
+            // this will only save the very last record of each type
+            RecordTypes.Type saveme = null;
+            int recordType = (int)record.getRecordType();
+            if (recordType == RecordTypes.PersistPtrIncrementalBlock.typeID) {
+                saveme = RecordTypes.PersistPtrIncrementalBlock;
+                ptr = (PersistPtrHolder)pdr;
+            } else if (recordType == RecordTypes.UserEditAtom.typeID) {
+                saveme = RecordTypes.UserEditAtom;
+                usr = (UserEditAtom)pdr;
+            }
+            if (interestingRecords != null && saveme != null) {
+                interestingRecords.put(saveme,pdr);
             }
             
             // Dummy write out, so the position winds on properly
-            record.writeOut(baos);
+            record.writeOut(cos);
         }
-        baos = null;
         
-        // For now, we're only handling PositionDependentRecord's that
-        // happen at the top level.
-        // In future, we'll need the handle them everywhere, but that's
-        // a bit trickier
-	    UserEditAtom usr = null;
-        for (Record record : _records) {
-            if (record instanceof PositionDependentRecord) {
-                // We've already figured out their new location, and
-                // told them that
-                // Tell them of the positions of the other records though
-                PositionDependentRecord pdr = (PositionDependentRecord)record;
-                pdr.updateOtherRecordReferences(oldToNewPositions);
-    
-                // Grab interesting records as they come past
-                // this will only save the very last record of each type
-                RecordTypes.Type saveme = null;
-                int recordType = (int)record.getRecordType();
-                if (recordType == RecordTypes.PersistPtrIncrementalBlock.typeID) {
-                    saveme = RecordTypes.PersistPtrIncrementalBlock;
-                } else if (recordType == RecordTypes.UserEditAtom.typeID) {
-                    saveme = RecordTypes.UserEditAtom;
-                    usr = (UserEditAtom)pdr;
-                }
-                if (interestingRecords != null && saveme != null) {
-                    interestingRecords.put(saveme,pdr);
-                }
-            }
+        assert(usr != null && ptr != null);
+        
+        Map<Integer,Integer> persistIds = new HashMap<Integer,Integer>();
+        for (Map.Entry<Integer,Integer> entry : ptr.getSlideLocationsLookup().entrySet()) {
+            persistIds.put(oldToNewPositions.get(entry.getValue()), entry.getKey());
+        }
+        
+        EncryptedSlideShow encData = new EncryptedSlideShow(getDocumentEncryptionAtom());
+	    
+	    for (Record record : _records) {
+            assert(record instanceof PositionDependentRecord);
+            // We've already figured out their new location, and
+            // told them that
+            // Tell them of the positions of the other records though
+            PositionDependentRecord pdr = (PositionDependentRecord)record;
+            Integer persistId = persistIds.get(pdr.getLastOnDiskOffset());
+            if (persistId == null) persistId = 0;
+            
+            // For now, we're only handling PositionDependentRecord's that
+            // happen at the top level.
+            // In future, we'll need the handle them everywhere, but that's
+            // a bit trickier
+            pdr.updateOtherRecordReferences(oldToNewPositions);
             
             // Whatever happens, write out that record tree
             if (os != null) {
-                record.writeOut(os);
+                record.writeOut(encData.encryptRecord(os, persistId, record));
             }
         }
 
@@ -504,7 +543,7 @@ public final class HSLFSlideShow extends
         }
         currentUser.setCurrentEditOffset(usr.getLastOnDiskOffset());
 	}
-	
+
     /**
      * Writes out the slideshow file the is represented by an instance
      *  of this class.
@@ -529,6 +568,16 @@ public final class HSLFSlideShow extends
      *           the passed in OutputStream
      */
     public void write(OutputStream out, boolean preserveNodes) throws IOException {
+        // read properties and pictures, with old encryption settings where appropriate 
+        if(_pictures == null) {
+           readPictures();
+        }
+        getDocumentSummaryInformation();
+
+        // set new encryption settings
+        EncryptedSlideShow encryptedSS = new EncryptedSlideShow(getDocumentEncryptionAtom());
+        _records = encryptedSS.updateEncryptionRecord(_records);
+
         // Get a new Filesystem to write into
         POIFSFileSystem outFS = new POIFSFileSystem();
 
@@ -537,8 +586,8 @@ public final class HSLFSlideShow extends
 
         // Write out the Property Streams
         writeProperties(outFS, writtenEntries);
-
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        
+        BufAccessBAOS baos = new BufAccessBAOS();
 
         // For position dependent records, hold where they were and now are
         // As we go along, update, and hand over, to any Position Dependent
@@ -546,27 +595,28 @@ public final class HSLFSlideShow extends
         updateAndWriteDependantRecords(baos, null);
 
         // Update our cached copy of the bytes that make up the PPT stream
-        _docstream = baos.toByteArray();
+        _docstream = new byte[baos.size()];
+        System.arraycopy(baos.getBuf(), 0, _docstream, 0, baos.size());
 
         // Write the PPT stream into the POIFS layer
-        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ByteArrayInputStream bais = new ByteArrayInputStream(_docstream);
         outFS.createDocument(bais,"PowerPoint Document");
         writtenEntries.add("PowerPoint Document");
+        
+        currentUser.setEncrypted(encryptedSS.getDocumentEncryptionAtom() != null);
         currentUser.writeToFS(outFS);
         writtenEntries.add("Current User");
 
 
-        // Write any pictures, into another stream
-        if(_pictures == null) {
-           readPictures();
-        }
         if (_pictures.size() > 0) {
-            ByteArrayOutputStream pict = new ByteArrayOutputStream();
+            BufAccessBAOS pict = new BufAccessBAOS();
             for (PictureData p : _pictures) {
+                int offset = pict.size();
                 p.write(pict);
+                encryptedSS.encryptPicture(pict.getBuf(), offset);
             }
             outFS.createDocument(
-                new ByteArrayInputStream(pict.toByteArray()), "Pictures"
+                new ByteArrayInputStream(pict.getBuf(), 0, pict.size()), "Pictures"
             );
             writtenEntries.add("Pictures");
         }
@@ -580,8 +630,44 @@ public final class HSLFSlideShow extends
         outFS.writeFilesystem(out);
     }
 
+    /** 
+     * For a given named property entry, either return it or null if
+     *  if it wasn't found
+     *  
+     *  @param setName The property to read
+     *  @return The value of the given property or null if it wasn't found.
+     */
+    protected PropertySet getPropertySet(String setName) {
+        DocumentEncryptionAtom dea = getDocumentEncryptionAtom();
+        return (dea == null)
+            ? super.getPropertySet(setName)
+            : super.getPropertySet(setName, dea.getEncryptionInfo());
+    }
 
-	/* ******************* adding methods follow ********************* */
+    /**
+     * Writes out the standard Documment Information Properties (HPSF)
+     * @param outFS the POIFSFileSystem to write the properties into
+     * @param writtenEntries a list of POIFS entries to add the property names too
+     * 
+     * @throws IOException if an error when writing to the 
+     *      {@link POIFSFileSystem} occurs
+     */
+    protected void writeProperties(POIFSFileSystem outFS, List<String> writtenEntries) throws IOException {
+        super.writeProperties(outFS, writtenEntries);
+        DocumentEncryptionAtom dea = getDocumentEncryptionAtom();
+        if (dea != null) {
+            CryptoAPIEncryptor enc = (CryptoAPIEncryptor)dea.getEncryptionInfo().getEncryptor();
+            try {
+                enc.getDataStream(outFS.getRoot()); // ignore OutputStream
+            } catch (IOException e) {
+                throw e;
+            } catch (GeneralSecurityException e) {
+                throw new IOException(e);
+            }
+        }
+    }
+    
+    /* ******************* adding methods follow ********************* */
 
 	/**
 	 * Adds a new root level record, at the end, but before the last
@@ -688,4 +774,30 @@ public final class HSLFSlideShow extends
         }
         return _objects;
     }
+    
+    
+    private static class BufAccessBAOS extends ByteArrayOutputStream {
+        public byte[] getBuf() {
+            return buf;
+        }
+    }
+    
+    private static class CountingOS extends OutputStream {
+        int count = 0;
+        public void write(int b) throws IOException {
+            count++;
+        }
+
+        public void write(byte[] b) throws IOException {
+            count += b.length;
+        }
+
+        public void write(byte[] b, int off, int len) throws IOException {
+            count += len;
+        }
+        
+        public int size() {
+            return count;
+        }
+    }
 }

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hslf/dev/SlideIdListing.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hslf/dev/SlideIdListing.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hslf/dev/SlideIdListing.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hslf/dev/SlideIdListing.java Thu Dec 25 01:56:29 2014
@@ -17,15 +17,23 @@
 
 package org.apache.poi.hslf.dev;
 
-import org.apache.poi.hslf.*;
-import org.apache.poi.hslf.record.*;
-import org.apache.poi.hslf.usermodel.SlideShow;
+import java.io.ByteArrayOutputStream;
+import java.util.Map;
 
+import org.apache.poi.hslf.HSLFSlideShow;
+import org.apache.poi.hslf.record.Document;
+import org.apache.poi.hslf.record.Notes;
+import org.apache.poi.hslf.record.NotesAtom;
+import org.apache.poi.hslf.record.PersistPtrHolder;
+import org.apache.poi.hslf.record.PositionDependentRecord;
+import org.apache.poi.hslf.record.Record;
+import org.apache.poi.hslf.record.Slide;
+import org.apache.poi.hslf.record.SlideAtom;
+import org.apache.poi.hslf.record.SlideListWithText;
+import org.apache.poi.hslf.record.SlidePersistAtom;
+import org.apache.poi.hslf.usermodel.SlideShow;
 import org.apache.poi.util.LittleEndian;
 
-import java.io.*;
-import java.util.Hashtable;
-
 /**
  * Gets all the different things that have Slide IDs (of sorts)
  *  in them, and displays them, so you can try to guess what they
@@ -122,10 +130,10 @@ public final class SlideIdListing {
 
 				// Check the sheet offsets
 				int[] sheetIDs = pph.getKnownSlideIDs();
-				Hashtable sheetOffsets = pph.getSlideLocationsLookup();
+				Map<Integer,Integer> sheetOffsets = pph.getSlideLocationsLookup();
 				for(int j=0; j<sheetIDs.length; j++) {
-					Integer id = Integer.valueOf(sheetIDs[j]);
-					Integer offset = (Integer)sheetOffsets.get(id);
+					Integer id = sheetIDs[j];
+					Integer offset = sheetOffsets.get(id);
 
 					System.out.println("  Knows about sheet " + id);
 					System.out.println("    That sheet lives at " + offset);

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hslf/dev/UserEditAndPersistListing.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hslf/dev/UserEditAndPersistListing.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hslf/dev/UserEditAndPersistListing.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hslf/dev/UserEditAndPersistListing.java Thu Dec 25 01:56:29 2014
@@ -18,10 +18,14 @@
 package org.apache.poi.hslf.dev;
 
 import java.io.ByteArrayOutputStream;
-import java.util.Hashtable;
+import java.util.Map;
 
 import org.apache.poi.hslf.HSLFSlideShow;
-import org.apache.poi.hslf.record.*;
+import org.apache.poi.hslf.record.CurrentUserAtom;
+import org.apache.poi.hslf.record.PersistPtrHolder;
+import org.apache.poi.hslf.record.PositionDependentRecord;
+import org.apache.poi.hslf.record.Record;
+import org.apache.poi.hslf.record.UserEditAtom;
 import org.apache.poi.util.LittleEndian;
 
 /**
@@ -61,10 +65,10 @@ public final class UserEditAndPersistLis
 
 				// Check the sheet offsets
 				int[] sheetIDs = pph.getKnownSlideIDs();
-				Hashtable sheetOffsets = pph.getSlideLocationsLookup();
+				Map<Integer,Integer> sheetOffsets = pph.getSlideLocationsLookup();
 				for(int j=0; j<sheetIDs.length; j++) {
-					Integer id = Integer.valueOf(sheetIDs[j]);
-					Integer offset = (Integer)sheetOffsets.get(id);
+					Integer id = sheetIDs[j];
+					Integer offset = sheetOffsets.get(id);
 
 					System.out.println("  Knows about sheet " + id);
 					System.out.println("    That sheet lives at " + offset);

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hslf/exceptions/CorruptPowerPointFileException.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hslf/exceptions/CorruptPowerPointFileException.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hslf/exceptions/CorruptPowerPointFileException.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hslf/exceptions/CorruptPowerPointFileException.java Thu Dec 25 01:56:29 2014
@@ -29,4 +29,12 @@ public final class CorruptPowerPointFile
 	public CorruptPowerPointFileException(String s) {
 		super(s);
 	}
+	
+	public CorruptPowerPointFileException(String s, Throwable t) {
+	    super(s,t);
+	}
+
+    public CorruptPowerPointFileException(Throwable t) {
+        super(t);
+    }
 }

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hslf/exceptions/EncryptedPowerPointFileException.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hslf/exceptions/EncryptedPowerPointFileException.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hslf/exceptions/EncryptedPowerPointFileException.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hslf/exceptions/EncryptedPowerPointFileException.java Thu Dec 25 01:56:29 2014
@@ -28,4 +28,12 @@ public final class EncryptedPowerPointFi
 	public EncryptedPowerPointFileException(String s) {
 		super(s);
 	}
+	
+	public EncryptedPowerPointFileException(String s, Throwable t) {
+        super(s, t);
+	}
+
+    public EncryptedPowerPointFileException(Throwable t) {
+        super(t);
+    }
 }

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/CurrentUserAtom.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/CurrentUserAtom.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/CurrentUserAtom.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/CurrentUserAtom.java Thu Dec 25 01:56:29 2014
@@ -20,15 +20,21 @@
 
 package org.apache.poi.hslf.record;
 
-import java.io.*;
-import org.apache.poi.poifs.filesystem.*;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
+import org.apache.poi.hslf.exceptions.OldPowerPointFormatException;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.DocumentEntry;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
 import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
 import org.apache.poi.util.StringUtil;
-import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
-import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
-import org.apache.poi.hslf.exceptions.OldPowerPointFormatException;
 
 
 /**
@@ -47,7 +53,7 @@ public class CurrentUserAtom
 	public static final byte[] atomHeader = new byte[] { 0, 0, -10, 15 };
 	/** The PowerPoint magic number for a non-encrypted file */
 	public static final byte[] headerToken = new byte[] { 95, -64, -111, -29 };
-	/** The PowerPoint magic number for an encrytpted file */ 
+	/** The PowerPoint magic number for an encrypted file */ 
 	public static final byte[] encHeaderToken = new byte[] { -33, -60, -47, -13 };
 	/** The Powerpoint 97 version, major and minor numbers */
 	public static final byte[] ppt97FileVer = new byte[] { 8, 00, -13, 03, 03, 00 };
@@ -66,6 +72,9 @@ public class CurrentUserAtom
 
 	/** Only correct after reading in or writing out */
 	private byte[] _contents;
+	
+	/** Flag for encryption state of the whole file */
+	private boolean isEncrypted;
 
 
 	/* ********************* getter/setter follows *********************** */
@@ -84,6 +93,9 @@ public class CurrentUserAtom
 	public String getLastEditUsername() { return lastEditUser; }
 	public void setLastEditUsername(String u) { lastEditUser = u; }
 
+	public boolean isEncrypted() { return isEncrypted; }
+	public void setEncrypted(boolean isEncrypted) { this.isEncrypted = isEncrypted; }
+	
 
 	/* ********************* real code follows *************************** */
 
@@ -100,6 +112,7 @@ public class CurrentUserAtom
 		releaseVersion = 8;
 		currentEditOffset = 0;
 		lastEditUser = "Apache POI";
+		isEncrypted = false;
 	}
 
 	/** 
@@ -157,14 +170,10 @@ public class CurrentUserAtom
 	 */
 	private void init() {
 		// First up is the size, in 4 bytes, which is fixed
-		// Then is the header - check for encrypted
-		if(_contents[12] == encHeaderToken[0] && 
-			_contents[13] == encHeaderToken[1] &&
-			_contents[14] == encHeaderToken[2] &&
-			_contents[15] == encHeaderToken[3]) {
-			throw new EncryptedPowerPointFileException("The CurrentUserAtom specifies that the document is encrypted");
-		}
+		// Then is the header
 		
+	    isEncrypted = (LittleEndian.getInt(encHeaderToken) == LittleEndian.getInt(_contents,12)); 
+	    
 		// Grab the edit offset
 		currentEditOffset = LittleEndian.getUInt(_contents,16);
 
@@ -229,7 +238,7 @@ public class CurrentUserAtom
 		LittleEndian.putInt(_contents,8,20);
 
 		// Now the ppt un-encrypted header token (4 bytes)
-		System.arraycopy(headerToken,0,_contents,12,4);
+		System.arraycopy((isEncrypted ? encHeaderToken : headerToken),0,_contents,12,4);
 
 		// Now the current edit offset
 		LittleEndian.putInt(_contents,16,(int)currentEditOffset);

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java Thu Dec 25 01:56:29 2014
@@ -17,10 +17,20 @@
 
 package org.apache.poi.hslf.record;
 
-import org.apache.poi.util.StringUtil;
-
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Hashtable;
+
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionMode;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionHeader;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionVerifier;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianInputStream;
 
 /**
  * A Document Encryption Atom (type 12052). Holds information
@@ -28,56 +38,64 @@ import java.io.OutputStream;
  *
  * @author Nick Burch
  */
-public final class DocumentEncryptionAtom extends RecordAtom {
+public final class DocumentEncryptionAtom extends PositionDependentRecordAtom {
+    private static long _type = 12052l;
 	private byte[] _header;
-	private static long _type = 12052l;
-
-	private byte[] data;
-	private String encryptionProviderName;
+	private EncryptionInfo ei;
 
 	/**
 	 * For the Document Encryption Atom
 	 */
-	protected DocumentEncryptionAtom(byte[] source, int start, int len) {
+	protected DocumentEncryptionAtom(byte[] source, int start, int len) throws IOException {
 		// 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);
+		ByteArrayInputStream bis = new ByteArrayInputStream(source, start+8, len-8);
+		LittleEndianInputStream leis = new LittleEndianInputStream(bis);
+		ei = new EncryptionInfo(leis, true);
 	}
 
+	public DocumentEncryptionAtom() {
+	    _header = new byte[8];
+	    LittleEndian.putShort(_header, 0, (short)0x000F);
+	    LittleEndian.putShort(_header, 2, (short)_type);
+	    // record length not yet known ...
+	    
+	    ei = new EncryptionInfo(EncryptionMode.cryptoAPI);
+	}
+	
+	/**
+	 * Initializes the encryption settings
+	 *
+	 * @param keyBits see {@link CipherAlgorithm#rc4} for allowed values, use -1 for default size
+	 */
+	public void initializeEncryptionInfo(int keyBits) {
+	    ei = new EncryptionInfo(EncryptionMode.cryptoAPI, CipherAlgorithm.rc4, HashAlgorithm.sha1, keyBits, -1, null);
+	}
+	
 	/**
 	 * Return the length of the encryption key, in bits
 	 */
 	public int getKeyLength() {
-		return data[28];
+		return ei.getHeader().getKeySize();
 	}
 
 	/**
 	 * Return the name of the encryption provider used
 	 */
 	public String getEncryptionProviderName() {
-		return encryptionProviderName;
+		return ei.getHeader().getCspName();
 	}
 
-
+	/**
+	 * @return the {@link EncryptionInfo} object for details about encryption settings
+	 */
+	public EncryptionInfo getEncryptionInfo() {
+	    return ei;
+	}
+	
+	
 	/**
 	 * We are of type 12052
 	 */
@@ -88,10 +106,24 @@ public final class DocumentEncryptionAto
 	 *  to disk
 	 */
 	public void writeOut(OutputStream out) throws IOException {
-		// Header
-		out.write(_header);
 
 		// Data
-		out.write(data);
+		byte data[] = new byte[1024];
+		LittleEndianByteArrayOutputStream bos = new LittleEndianByteArrayOutputStream(data, 0);
+		bos.writeShort(ei.getVersionMajor());
+		bos.writeShort(ei.getVersionMinor());
+		bos.writeInt(ei.getEncryptionFlags());
+		
+		((CryptoAPIEncryptionHeader)ei.getHeader()).write(bos);
+		((CryptoAPIEncryptionVerifier)ei.getVerifier()).write(bos);
+		
+        // Header
+		LittleEndian.putInt(_header, 4, bos.getWriteIndex());
+        out.write(_header);
+		out.write(data, 0, bos.getWriteIndex());
 	}
+
+    public void updateOtherRecordReferences(Hashtable<Integer,Integer> oldToNewReferencesLookup) {
+        
+    }
 }

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/PersistPtrHolder.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/PersistPtrHolder.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/PersistPtrHolder.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/PersistPtrHolder.java Thu Dec 25 01:56:29 2014
@@ -17,13 +17,17 @@
 
 package org.apache.poi.hslf.record;
 
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.POILogger;
-
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.util.Enumeration;
 import java.util.Hashtable;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogger;
 
 /**
  * General holder for PersistPtrFullBlock and PersistPtrIncrementalBlock
@@ -49,12 +53,14 @@ public final class PersistPtrHolder exte
 	 *  that knows about a given slide to find the right location
 	 */
 	private Hashtable<Integer,Integer> _slideLocations;
-	/**
-	 * Holds the lookup from slide id to where their offset is
-	 *  held inside _ptrData. Used when writing out, and updating
-	 *  the positions of the slides
-	 */
-	private Hashtable<Integer,Integer> _slideOffsetDataLocation;
+
+	private static final BitField persistIdFld = new BitField(0X000FFFFF);
+	private static final BitField cntPersistFld  = new BitField(0XFFF00000);
+	
+    /**
+     * Return the value we were given at creation, be it 6001 or 6002
+     */
+    public long getRecordType() { return _type; }
 
 	/**
 	 * Get the list of slides that this PersistPtrHolder knows about.
@@ -63,10 +69,9 @@ public final class PersistPtrHolder exte
 	 */
 	public int[] getKnownSlideIDs() {
 		int[] ids = new int[_slideLocations.size()];
-		Enumeration<Integer> e = _slideLocations.keys();
-		for(int i=0; i<ids.length; i++) {
-			Integer id = e.nextElement();
-			ids[i] = id.intValue();
+		int i = 0;
+		for (Integer slideId : _slideLocations.keySet()) {
+		    ids[i++] = slideId;
 		}
 		return ids;
 	}
@@ -78,46 +83,16 @@ public final class PersistPtrHolder exte
 	public Hashtable<Integer,Integer> getSlideLocationsLookup() {
 		return _slideLocations;
 	}
+	
 	/**
 	 * Get the lookup from slide numbers to their offsets inside
 	 *  _ptrData, used when adding or moving slides.
+	 * 
+	 * @deprecated since POI 3.11, not supported anymore
 	 */
+	@Deprecated
 	public Hashtable<Integer,Integer> getSlideOffsetDataLocationsLookup() {
-		return _slideOffsetDataLocation;
-	}
-
-	/**
-	 * Adds a new slide, notes or similar, to be looked up by this.
-	 * For now, won't look for the most optimal on disk representation.
-	 */
-	public void addSlideLookup(int slideID, int posOnDisk) {
-		// PtrData grows by 8 bytes:
-		//  4 bytes for the new info block
-		//  4 bytes for the slide offset
-		byte[] newPtrData = new byte[_ptrData.length + 8];
-		System.arraycopy(_ptrData,0,newPtrData,0,_ptrData.length);
-
-		// Add to the slide location lookup hash
-		_slideLocations.put(Integer.valueOf(slideID), Integer.valueOf(posOnDisk));
-		// Add to the ptrData offset lookup hash
-		_slideOffsetDataLocation.put(Integer.valueOf(slideID),
-				Integer.valueOf(_ptrData.length + 4));
-
-		// Build the info block
-		// First 20 bits = offset number = slide ID
-		// Remaining 12 bits = offset count = 1
-		int infoBlock = slideID;
-		infoBlock += (1 << 20);
-
-		// Write out the data for this
-		LittleEndian.putInt(newPtrData,newPtrData.length-8,infoBlock);
-		LittleEndian.putInt(newPtrData,newPtrData.length-4,posOnDisk);
-
-		// Save the new ptr data
-		_ptrData = newPtrData;
-
-		// Update the atom header
-		LittleEndian.putInt(_header,4,newPtrData.length);
+		throw new UnsupportedOperationException("PersistPtrHolder.getSlideOffsetDataLocationsLookup() is not supported since 3.12-Beta1");
 	}
 
 	/**
@@ -141,30 +116,27 @@ public final class PersistPtrHolder exte
 		//   count * 32 bit offsets
 		// Repeat as many times as you have data
 		_slideLocations = new Hashtable<Integer,Integer>();
-		_slideOffsetDataLocation = new Hashtable<Integer,Integer>();
 		_ptrData = new byte[len-8];
 		System.arraycopy(source,start+8,_ptrData,0,_ptrData.length);
 
 		int pos = 0;
 		while(pos < _ptrData.length) {
-			// Grab the info field
-			long info = LittleEndian.getUInt(_ptrData,pos);
+		    // Grab the info field
+			int info = LittleEndian.getInt(_ptrData,pos);
 
 			// First 20 bits = offset number
 			// Remaining 12 bits = offset count
-			int offset_count = (int)(info >> 20);
-			int offset_no = (int)(info - (offset_count << 20));
-//System.out.println("Info is " + info + ", count is " + offset_count + ", number is " + offset_no);
-
+            int offset_no = persistIdFld.getValue(info);
+			int offset_count = cntPersistFld.getValue(info);
+			
 			// Wind on by the 4 byte info header
 			pos += 4;
 
 			// Grab the offsets for each of the sheets
 			for(int i=0; i<offset_count; i++) {
 				int sheet_no = offset_no + i;
-				long sheet_offset = LittleEndian.getUInt(_ptrData,pos);
-				_slideLocations.put(Integer.valueOf(sheet_no), Integer.valueOf((int)sheet_offset));
-				_slideOffsetDataLocation.put(Integer.valueOf(sheet_no), Integer.valueOf(pos));
+				int sheet_offset = (int)LittleEndian.getUInt(_ptrData,pos);
+				_slideLocations.put(sheet_no, sheet_offset);
 
 				// Wind on by 4 bytes per sheet found
 				pos += 4;
@@ -172,48 +144,108 @@ public final class PersistPtrHolder exte
 		}
 	}
 
-	/**
-	 * Return the value we were given at creation, be it 6001 or 6002
-	 */
-	public long getRecordType() { return _type; }
+    /**
+     *  remove all slide references
+     *  
+     *  Convenience method provided, for easier reviewing of invocations
+     */
+    public void clear() {
+        _slideLocations.clear();
+    }
+    
+    /**
+     * Adds a new slide, notes or similar, to be looked up by this.
+     */
+    public void addSlideLookup(int slideID, int posOnDisk) {
+        if (_slideLocations.containsKey(slideID)) {
+            throw new CorruptPowerPointFileException("A record with persistId "+slideID+" already exists.");
+        }
+
+        _slideLocations.put(slideID, posOnDisk);
+    }
 
 	/**
 	 * At write-out time, update the references to the sheets to their
 	 *  new positions
 	 */
 	public void updateOtherRecordReferences(Hashtable<Integer,Integer> oldToNewReferencesLookup) {
-		int[] slideIDs = getKnownSlideIDs();
-
 		// Loop over all the slides we know about
 		// Find where they used to live, and where they now live
-		// Then, update the right bit of _ptrData with their new location
-		for(int i=0; i<slideIDs.length; i++) {
-			Integer id = Integer.valueOf(slideIDs[i]);
-			Integer oldPos = (Integer)_slideLocations.get(id);
-			Integer newPos = (Integer)oldToNewReferencesLookup.get(oldPos);
-
-			if(newPos == null) {
-				logger.log(POILogger.WARN, "Couldn't find the new location of the \"slide\" with id " + id + " that used to be at " + oldPos);
-				logger.log(POILogger.WARN, "Not updating the position of it, you probably won't be able to find it any more (if you ever could!)");
-				newPos = oldPos;
-			}
+	    for (Map.Entry<Integer,Integer> me : _slideLocations.entrySet()) {
+	        Integer oldPos = me.getValue();
+	        Integer newPos = oldToNewReferencesLookup.get(oldPos);
+
+            if (newPos == null) {
+                Integer id = me.getKey();
+                logger.log(POILogger.WARN, "Couldn't find the new location of the \"slide\" with id " + id + " that used to be at " + oldPos);
+                logger.log(POILogger.WARN, "Not updating the position of it, you probably won't be able to find it any more (if you ever could!)");
+            } else {
+                me.setValue(newPos);
+            }
+	    }
+	}
+
+	private void normalizePersistDirectory() {
+        TreeMap<Integer,Integer> orderedSlideLocations = new TreeMap<Integer,Integer>(_slideLocations);
+        
+        @SuppressWarnings("resource")
+        BufAccessBAOS bos = new BufAccessBAOS();
+        byte intbuf[] = new byte[4];
+        int lastPersistEntry = -1;
+        int lastSlideId = -1;
+        for (Map.Entry<Integer,Integer> me : orderedSlideLocations.entrySet()) {
+            int nextSlideId = me.getKey();
+            int offset = me.getValue();
+            try {
+                // Building the info block
+                // First 20 bits = offset number = slide ID (persistIdFld, i.e. first slide ID of a continuous group)
+                // Remaining 12 bits = offset count = 1 (cntPersistFld, i.e. continuous entries in a group)
+                
+                if (lastSlideId+1 == nextSlideId) {
+                    // use existing PersistDirectoryEntry, need to increase entry count
+                    assert(lastPersistEntry != -1);
+                    int infoBlock = LittleEndian.getInt(bos.getBuf(), lastPersistEntry);
+                    int entryCnt = cntPersistFld.getValue(infoBlock);
+                    infoBlock = cntPersistFld.setValue(infoBlock, entryCnt+1);
+                    LittleEndian.putInt(bos.getBuf(), lastPersistEntry, infoBlock);
+                } else {
+                    // start new PersistDirectoryEntry
+                    lastPersistEntry = bos.size();
+                    int infoBlock = persistIdFld.setValue(0, nextSlideId);
+                    infoBlock = cntPersistFld.setValue(infoBlock, 1);
+                    LittleEndian.putInt(intbuf, 0, infoBlock);
+                    bos.write(intbuf);
+                }
+                // Add to the ptrData offset lookup hash
+                LittleEndian.putInt(intbuf, 0, offset);
+                bos.write(intbuf);
+                lastSlideId = nextSlideId;
+            } catch (IOException e) {
+                // ByteArrayOutputStream is very unlikely throwing a IO exception (maybe because of OOM ...)
+                throw new RuntimeException(e);
+            }
+        }
+        
+        // Save the new ptr data
+        _ptrData = bos.toByteArray();
 
-			// Write out the new location
-			Integer dataOffset = (Integer)_slideOffsetDataLocation.get(id);
-			LittleEndian.putInt(_ptrData,dataOffset.intValue(),newPos.intValue());
-
-			// Update our hashtable
-			_slideLocations.remove(id);
-			_slideLocations.put(id,newPos);
-		}
+        // Update the atom header
+        LittleEndian.putInt(_header,4,bos.size());
 	}
-
+	
 	/**
 	 * Write the contents of the record back, so it can be written
 	 *  to disk
 	 */
 	public void writeOut(OutputStream out) throws IOException {
+	    normalizePersistDirectory();
 		out.write(_header);
 		out.write(_ptrData);
 	}
+	
+    private static class BufAccessBAOS extends ByteArrayOutputStream {
+        public byte[] getBuf() {
+            return buf;
+        }
+    }
 }

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/Record.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/Record.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/Record.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/Record.java Thu Dec 25 01:56:29 2014
@@ -74,7 +74,7 @@ public abstract class Record
 	 */
 	public static void writeLittleEndian(int i,OutputStream o) throws IOException {
 		byte[] bi = new byte[4];
-		LittleEndian.putInt(bi,i);
+		LittleEndian.putInt(bi,0,i);
 		o.write(bi);
 	}
 	/**
@@ -82,7 +82,7 @@ public abstract class Record
 	 */
 	public static void writeLittleEndian(short s,OutputStream o) throws IOException {
 		byte[] bs = new byte[2];
-		LittleEndian.putShort(bs,s);
+		LittleEndian.putShort(bs,0,s);
 		o.write(bs);
 	}
 

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/UserEditAtom.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/UserEditAtom.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/UserEditAtom.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/UserEditAtom.java Thu Dec 25 01:56:29 2014
@@ -18,6 +18,8 @@
 package org.apache.poi.hslf.record;
 
 import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
+
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.Hashtable;
@@ -42,7 +44,7 @@ public final class UserEditAtom extends
 
 	private byte[] _header;
 	private static long _type = 4085l;
-	private byte[] reserved;
+	private short unused;
 
 	private int lastViewedSlideID;
 	private int pptVersion;
@@ -51,6 +53,7 @@ public final class UserEditAtom extends
 	private int docPersistRef;
 	private int maxPersistWritten;
 	private short lastViewType;
+	private int encryptSessionPersistIdRef = -1;
 
 	// Somewhat user facing getters
 	public int getLastViewedSlideID() { return lastViewedSlideID; }
@@ -61,12 +64,17 @@ public final class UserEditAtom extends
 	public int getPersistPointersOffset()  { return persistPointersOffset; }
 	public int getDocPersistRef()          { return docPersistRef; }
 	public int getMaxPersistWritten()      { return maxPersistWritten; }
+	public int getEncryptSessionPersistIdRef() { return encryptSessionPersistIdRef; }
 
 	// More scary internal setters
 	public void setLastUserEditAtomOffset(int offset) { lastUserEditAtomOffset = offset; }
 	public void setPersistPointersOffset(int offset)  { persistPointersOffset = offset; }
 	public void setLastViewType(short type)           { lastViewType=type; }
-    public void setMaxPersistWritten(int max)           { maxPersistWritten=max; }
+    public void setMaxPersistWritten(int max)         { maxPersistWritten=max; }
+    public void setEncryptSessionPersistIdRef(int id) {
+        encryptSessionPersistIdRef=id;
+        LittleEndian.putInt(_header,4,(id == -1 ? 28 : 32));
+    }
 
 	/* *************** record code follows ********************** */
 
@@ -77,39 +85,56 @@ public final class UserEditAtom extends
 		// Sanity Checking
 		if(len < 34) { len = 34; }
 
+		int offset = start;
 		// Get the header
 		_header = new byte[8];
-		System.arraycopy(source,start,_header,0,8);
+		System.arraycopy(source,offset,_header,0,8);
+		offset += 8;
 
 		// Get the last viewed slide ID
-		lastViewedSlideID = LittleEndian.getInt(source,start+0+8);
+		lastViewedSlideID = LittleEndian.getInt(source,offset);
+		offset += LittleEndianConsts.INT_SIZE;
 
 		// Get the PPT version
-		pptVersion = LittleEndian.getInt(source,start+4+8);
+		pptVersion = LittleEndian.getInt(source,offset);
+		offset += LittleEndianConsts.INT_SIZE;
 
 		// Get the offset to the previous incremental save's UserEditAtom
 		// This will be the byte offset on disk where the previous one
 		//  starts, or 0 if this is the first one
-		lastUserEditAtomOffset = LittleEndian.getInt(source,start+8+8);
+		lastUserEditAtomOffset = LittleEndian.getInt(source,offset);
+		offset += LittleEndianConsts.INT_SIZE;
 
 		// Get the offset to the persist pointers
 		// This will be the byte offset on disk where the preceding
 		//  PersistPtrFullBlock or PersistPtrIncrementalBlock starts
-		persistPointersOffset = LittleEndian.getInt(source,start+12+8);
+		persistPointersOffset = LittleEndian.getInt(source,offset);
+		offset += LittleEndianConsts.INT_SIZE;
 
 		// Get the persist reference for the document persist object
 		// Normally seems to be 1
-		docPersistRef = LittleEndian.getInt(source,start+16+8);
+		docPersistRef = LittleEndian.getInt(source,offset);
+		offset += LittleEndianConsts.INT_SIZE;
 
 		// Maximum number of persist objects written
-		maxPersistWritten = LittleEndian.getInt(source,start+20+8);
+		maxPersistWritten = LittleEndian.getInt(source,offset);
+		offset += LittleEndianConsts.INT_SIZE;
 
 		// Last view type
-		lastViewType = LittleEndian.getShort(source,start+24+8);
+		lastViewType = LittleEndian.getShort(source,offset);
+		offset += LittleEndianConsts.SHORT_SIZE;
+		
+		// unused
+		unused = LittleEndian.getShort(source,offset);
+		offset += LittleEndianConsts.SHORT_SIZE;
 
 		// There might be a few more bytes, which are a reserved field
-		reserved = new byte[len-26-8];
-		System.arraycopy(source,start+26+8,reserved,0,reserved.length);
+		if (offset-start<len) {
+		    encryptSessionPersistIdRef = LittleEndian.getInt(source,offset);
+		    offset += LittleEndianConsts.INT_SIZE;
+		}
+		
+		assert(offset-start == len);
 	}
 
 	/**
@@ -155,8 +180,10 @@ public final class UserEditAtom extends
 		writeLittleEndian(docPersistRef,out);
 		writeLittleEndian(maxPersistWritten,out);
 		writeLittleEndian(lastViewType,out);
-
-		// Reserved fields
-		out.write(reserved);
+		writeLittleEndian(unused,out);
+		if (encryptSessionPersistIdRef != -1) {
+		    // optional field
+		    writeLittleEndian(encryptSessionPersistIdRef,out);
+		}
 	}
 }

Modified: poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/AllHSLFRecordTests.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/AllHSLFRecordTests.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/AllHSLFRecordTests.java (original)
+++ poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/AllHSLFRecordTests.java Thu Dec 25 01:56:29 2014
@@ -17,56 +17,54 @@
 
 package org.apache.poi.hslf.record;
 
-import junit.framework.Test;
-import junit.framework.TestSuite;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
 
 /**
  * Collects all tests from the package <tt>org.apache.poi.hslf.record</tt>.
  * 
  * @author Josh Micich
  */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    TestAnimationInfoAtom.class,
+    TestCString.class,
+    TestColorSchemeAtom.class,
+    TestComment2000.class,
+    TestComment2000Atom.class,
+    TestCurrentUserAtom.class,
+    TestDocument.class,
+    TestDocumentAtom.class,
+    TestDocumentEncryptionAtom.class,
+    TestExControl.class,
+    TestExHyperlink.class,
+    TestExHyperlinkAtom.class,
+    TestExMediaAtom.class,
+    TestExObjList.class,
+    TestExObjListAtom.class,
+    TestExOleObjAtom.class,
+    TestExOleObjStg.class,
+    TestExVideoContainer.class,
+    TestFontCollection.class,
+    TestHeadersFootersAtom.class,
+    TestHeadersFootersContainer.class,
+    TestInteractiveInfo.class,
+    TestInteractiveInfoAtom.class,
+    TestNotesAtom.class,
+    TestRecordContainer.class,
+    TestRecordTypes.class,
+    TestSlideAtom.class,
+    TestSlidePersistAtom.class,
+    TestSound.class,
+    TestStyleTextPropAtom.class,
+    TestTextBytesAtom.class,
+    TestTextCharsAtom.class,
+    TestTextHeaderAtom.class,
+    TestTextRulerAtom.class,
+    TestTextSpecInfoAtom.class,
+    TestTxInteractiveInfoAtom.class,
+    TestTxMasterStyleAtom.class,
+    TestUserEditAtom.class
+})
 public class AllHSLFRecordTests {
-	
-	public static Test suite() {
-		TestSuite result = new TestSuite(AllHSLFRecordTests.class.getName());
-		result.addTestSuite(TestAnimationInfoAtom.class);
-		result.addTestSuite(TestCString.class);
-		result.addTestSuite(TestColorSchemeAtom.class);
-		result.addTestSuite(TestComment2000.class);
-		result.addTestSuite(TestComment2000Atom.class);
-		result.addTestSuite(TestCurrentUserAtom.class);
-		result.addTestSuite(TestDocument.class);
-		result.addTestSuite(TestDocumentAtom.class);
-		result.addTestSuite(TestDocumentEncryptionAtom.class);
-		result.addTestSuite(TestExControl.class);
-		result.addTestSuite(TestExHyperlink.class);
-		result.addTestSuite(TestExHyperlinkAtom.class);
-		result.addTestSuite(TestExMediaAtom.class);
-		result.addTestSuite(TestExObjList.class);
-		result.addTestSuite(TestExObjListAtom.class);
-		result.addTestSuite(TestExOleObjAtom.class);
-		result.addTestSuite(TestExOleObjStg.class);
-		result.addTestSuite(TestExVideoContainer.class);
-		result.addTestSuite(TestFontCollection.class);
-		result.addTestSuite(TestHeadersFootersAtom.class);
-		result.addTestSuite(TestHeadersFootersContainer.class);
-		result.addTestSuite(TestInteractiveInfo.class);
-		result.addTestSuite(TestInteractiveInfoAtom.class);
-		result.addTestSuite(TestNotesAtom.class);
-		result.addTestSuite(TestRecordContainer.class);
-		result.addTestSuite(TestRecordTypes.class);
-		result.addTestSuite(TestSlideAtom.class);
-		result.addTestSuite(TestSlidePersistAtom.class);
-		result.addTestSuite(TestSound.class);
-		result.addTestSuite(TestStyleTextPropAtom.class);
-		result.addTestSuite(TestTextBytesAtom.class);
-		result.addTestSuite(TestTextCharsAtom.class);
-		result.addTestSuite(TestTextHeaderAtom.class);
-		result.addTestSuite(TestTextRulerAtom.class);
-		result.addTestSuite(TestTextSpecInfoAtom.class);
-		result.addTestSuite(TestTxInteractiveInfoAtom.class);
-		result.addTestSuite(TestTxMasterStyleAtom.class);
-		result.addTestSuite(TestUserEditAtom.class);
-		return result;
-	}
 }

Modified: poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCurrentUserAtom.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCurrentUserAtom.java?rev=1647867&r1=1647866&r2=1647867&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCurrentUserAtom.java (original)
+++ poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCurrentUserAtom.java Thu Dec 25 01:56:29 2014
@@ -17,36 +17,33 @@
 
 package org.apache.poi.hslf.record;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-import junit.framework.TestCase;
 import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
 
+import org.apache.poi.POIDataSamples;
+import org.apache.poi.hslf.HSLFSlideShow;
 import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
 import org.apache.poi.poifs.filesystem.DocumentEntry;
 import org.apache.poi.poifs.filesystem.POIFSFileSystem;
-import org.apache.poi.POIDataSamples;
+import org.junit.Test;
 
 /**
  * Tests that CurrentUserAtom works properly.
  *
  * @author Nick Burch (nick at torchbox dot com)
  */
-public final class TestCurrentUserAtom extends TestCase {
+public final class TestCurrentUserAtom {
     private static POIDataSamples _slTests = POIDataSamples.getSlideShowInstance();
 	/** Not encrypted */
-	private String normalFile;
+	private static final String normalFile = "basic_test_ppt_file.ppt";
 	/** Encrypted */
-	private String encFile;
-
-	protected void setUp() throws Exception {
-		super.setUp();
-
-		normalFile = "basic_test_ppt_file.ppt";
-		encFile = "Password_Protected-hello.ppt";
-	}
+	private static final String encFile = "Password_Protected-hello.ppt";
 
-	public void testReadNormal() throws Exception {
+	@Test
+	public void readNormal() throws Exception {
 		POIFSFileSystem fs = new POIFSFileSystem(
 				_slTests.openResourceAsStream(normalFile)
 		);
@@ -66,20 +63,20 @@ public final class TestCurrentUserAtom e
 		assertEquals(0x2942, cu2.getCurrentEditOffset());
 	}
 
-	public void testReadEnc() throws Exception {
+	@Test(expected = EncryptedPowerPointFileException.class)
+	public void readEnc() throws Exception {
 		POIFSFileSystem fs = new POIFSFileSystem(
 				_slTests.openResourceAsStream(encFile)
 		);
 
-		try {
-			new CurrentUserAtom(fs);
-			fail();
-		} catch(EncryptedPowerPointFileException e) {
-			// Good
-		}
+		new CurrentUserAtom(fs);
+		assertTrue(true); // not yet failed
+		
+		new HSLFSlideShow(fs);
 	}
 
-	public void testWriteNormal() throws Exception {
+	@Test
+	public void writeNormal() throws Exception {
 		// Get raw contents from a known file
 		POIFSFileSystem fs = new POIFSFileSystem(
 				_slTests.openResourceAsStream(normalFile)

Added: poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/TestDocumentEncryption.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/TestDocumentEncryption.java?rev=1647867&view=auto
==============================================================================
--- poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/TestDocumentEncryption.java (added)
+++ poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/TestDocumentEncryption.java Thu Dec 25 01:56:29 2014
@@ -0,0 +1,182 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hslf.record;
+
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.security.MessageDigest;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.poi.POIDataSamples;
+import org.apache.poi.hpsf.DocumentSummaryInformation;
+import org.apache.poi.hpsf.PropertySet;
+import org.apache.poi.hpsf.PropertySetFactory;
+import org.apache.poi.hpsf.SummaryInformation;
+import org.apache.poi.hslf.HSLFSlideShow;
+import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
+import org.apache.poi.hslf.model.Slide;
+import org.apache.poi.hslf.usermodel.PictureData;
+import org.apache.poi.hslf.usermodel.SlideShow;
+import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionHeader;
+import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests that DocumentEncryption works properly.
+ */
+public class TestDocumentEncryption {
+    POIDataSamples slTests = POIDataSamples.getSlideShowInstance();
+
+    @Before
+    public void resetPassword() {
+        Biff8EncryptionKey.setCurrentUserPassword(null);
+    }
+    
+    @Test
+    public void cryptoAPIDecryptionOther() throws Exception {
+        Biff8EncryptionKey.setCurrentUserPassword("hello");
+        String encPpts[] = {
+            "Password_Protected-56-hello.ppt",
+            "Password_Protected-hello.ppt",
+            "Password_Protected-np-hello.ppt",
+        };
+        
+        for (String pptFile : encPpts) {
+            try {
+                NPOIFSFileSystem fs = new NPOIFSFileSystem(slTests.getFile(pptFile), true);
+                HSLFSlideShow hss = new HSLFSlideShow(fs);
+                new SlideShow(hss);
+                fs.close();
+            } catch (EncryptedPowerPointFileException e) {
+                fail(pptFile+" can't be decrypted");
+            }
+        }
+    }
+
+    @Test
+    public void cryptoAPIChangeKeySize() throws Exception {
+        String pptFile = "cryptoapi-proc2356.ppt";
+        Biff8EncryptionKey.setCurrentUserPassword("crypto");
+        NPOIFSFileSystem fs = new NPOIFSFileSystem(slTests.getFile(pptFile), true);
+        HSLFSlideShow hss = new HSLFSlideShow(fs);
+        // need to cache data (i.e. read all data) before changing the key size
+        PictureData picsExpected[] = hss.getPictures();
+        hss.getDocumentSummaryInformation();
+        EncryptionInfo ei = hss.getDocumentEncryptionAtom().getEncryptionInfo();
+        ((CryptoAPIEncryptionHeader)ei.getHeader()).setKeySize(0x78);
+        
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        hss.write(bos);
+        fs.close();
+        
+        fs = new NPOIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));
+        hss = new HSLFSlideShow(fs);
+        PictureData picsActual[] = hss.getPictures();
+        fs.close();
+        
+        assertEquals(picsExpected.length, picsActual.length);
+        for (int i=0; i<picsExpected.length; i++) {
+            assertArrayEquals(picsExpected[i].getRawData(), picsActual[i].getRawData());
+        }
+    }
+
+    @Test
+    public void cryptoAPIEncryption() throws Exception {
+        /* documents with multiple edits need to be normalized for encryption */
+        String pptFile = "57272_corrupted_usereditatom.ppt";
+        NPOIFSFileSystem fs = new NPOIFSFileSystem(slTests.getFile(pptFile), true);
+        HSLFSlideShow hss = new HSLFSlideShow(fs);
+        hss.normalizeRecords();
+        
+        // normalized ppt
+        ByteArrayOutputStream expected = new ByteArrayOutputStream();
+        hss.write(expected);
+        
+        // encrypted
+        Biff8EncryptionKey.setCurrentUserPassword("hello");
+        ByteArrayOutputStream encrypted = new ByteArrayOutputStream();
+        hss.write(encrypted);
+        fs.close();
+
+        // decrypted
+        ByteArrayInputStream bis = new ByteArrayInputStream(encrypted.toByteArray());
+        fs = new NPOIFSFileSystem(bis);
+        hss = new HSLFSlideShow(fs);
+        Biff8EncryptionKey.setCurrentUserPassword(null);
+        ByteArrayOutputStream actual = new ByteArrayOutputStream();
+        hss.write(actual);
+        fs.close();
+        
+        assertArrayEquals(expected.toByteArray(), actual.toByteArray());
+    }    
+    
+    @Test
+    public void cryptoAPIDecryption() throws Exception {
+        // taken from a msdn blog:
+        // http://blogs.msdn.com/b/openspecification/archive/2009/05/08/dominic-salemno.aspx
+        Biff8EncryptionKey.setCurrentUserPassword("crypto");
+        NPOIFSFileSystem fs = new NPOIFSFileSystem(slTests.getFile("cryptoapi-proc2356.ppt"));
+        HSLFSlideShow hss = new HSLFSlideShow(fs);
+        SlideShow ss = new SlideShow(hss);
+        
+        Slide slide = ss.getSlides()[0];
+        assertEquals("Dominic Salemno", slide.getTextRuns()[0].getText());
+
+        String picCmp[][] = {
+            {"0","nKsDTKqxTCR8LFkVVWlP9GSTvZ0="},
+            {"95163","SuNOR+9V1UVYZIoeD65l3VTaLoc="},
+            {"100864","Ql3IGrr4bNq07ZTp5iPg7b+pva8="},
+            {"714114","8pdst9NjBGSfWezSZE8+aVhIRe0="},
+            {"723752","go6xqW7lvkCtlOO5tYLiMfb4oxw="},
+            {"770128","gZUM8YqRNL5kGNfyyYvEEernvCc="},
+            {"957958","CNU2iiqUFAnk3TDXsXV1ihH9eRM="},                
+        };
+        
+        MessageDigest md = CryptoFunctions.getMessageDigest(HashAlgorithm.sha1);
+        PictureData pd[] = hss.getPictures();
+        int i = 0;
+        for (PictureData p : pd) {
+            byte hash[] = md.digest(p.getData());
+            assertEquals(Integer.parseInt(picCmp[i][0]), p.getOffset());
+            assertEquals(picCmp[i][1], Base64.encodeBase64String(hash));
+            i++;
+        }
+        
+        DocumentEncryptionAtom dea = hss.getDocumentEncryptionAtom();
+        
+        POIFSFileSystem fs2 = new POIFSFileSystem(dea.getEncryptionInfo().getDecryptor().getDataStream(fs));
+        PropertySet ps = PropertySetFactory.create(fs2.getRoot(), SummaryInformation.DEFAULT_STREAM_NAME);
+        assertTrue(ps.isSummaryInformation());
+        assertEquals("RC4 CryptoAPI Encryption", ps.getProperties()[1].getValue());
+        ps = PropertySetFactory.create(fs2.getRoot(), DocumentSummaryInformation.DEFAULT_STREAM_NAME);
+        assertTrue(ps.isDocumentSummaryInformation());
+        assertEquals("On-screen Show (4:3)", ps.getProperties()[1].getValue());
+    }
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org


Mime
View raw message