From fa669818ce7b32f26d0b85551e98c8a3dd0806a1 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Tue, 29 Jul 2008 21:40:47 +0000 Subject: [PATCH] Support for creating new HSLF CurrentUserAtoms git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@680853 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 1 + src/documentation/content/xdocs/status.xml | 1 + .../poi/hslf/record/CurrentUserAtom.java | 57 ++++++--- .../poi/hslf/record/TestCurrentUserAtom.java | 115 ++++++++++++++++++ 4 files changed, 156 insertions(+), 18 deletions(-) create mode 100644 src/scratchpad/testcases/org/apache/poi/hslf/record/TestCurrentUserAtom.java diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index c1424c4d04..12763c3d3d 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + Support for creating new HSLF CurrentUserAtoms 45466 - Partial support for removing excel comments (won't work for all excel versions yet) 45437 - Detect encrypted word documents, and throw an EncryptedDocumentException instead of a OOM 45404 - New class, hssf.usermodel.HSSFDataFormatter, for formatting numbers and dates in the same way that Excel does diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 8d8f567674..230244190c 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + Support for creating new HSLF CurrentUserAtoms 45466 - Partial support for removing excel comments (won't work for all excel versions yet) 45437 - Detect encrypted word documents, and throw an EncryptedDocumentException instead of a OOM 45404 - New class, hssf.usermodel.HSSFDataFormatter, for formatting numbers and dates in the same way that Excel does diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/CurrentUserAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/CurrentUserAtom.java index e0810dbca1..fb669b38ea 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/CurrentUserAtom.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/CurrentUserAtom.java @@ -25,6 +25,7 @@ import org.apache.poi.poifs.filesystem.*; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.StringUtil; import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException; +import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException; /** @@ -39,14 +40,15 @@ public class CurrentUserAtom { /** Standard Atom header */ public static final byte[] atomHeader = new byte[] { 0, 0, -10, 15 }; - /** The Powerpoint magic numer */ - public static final byte[] magicNumber = new byte[] { 95, -64, -111, -29 }; + /** 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 */ + 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 }; /** The version, major and minor numbers */ - private int docFinalVersionA; - private int docFinalVersionB; + private int docFinalVersion; private byte docMajorNo; private byte docMinorNo; @@ -54,7 +56,7 @@ public class CurrentUserAtom private long currentEditOffset; /** The Username of the last person to edit the file */ private String lastEditUser; - /** The document release version */ + /** The document release version. Almost always 8 */ private long releaseVersion; /** Only correct after reading in or writing out */ @@ -63,8 +65,7 @@ public class CurrentUserAtom /* ********************* getter/setter follows *********************** */ - public int getDocFinalVersionA() { return docFinalVersionA; } - public int getDocFinalVersionB() { return docFinalVersionB; } + public int getDocFinalVersion() { return docFinalVersion; } public byte getDocMajorNo() { return docMajorNo; } public byte getDocMinorNo() { return docMinorNo; } @@ -86,7 +87,14 @@ public class CurrentUserAtom */ public CurrentUserAtom() { _contents = new byte[0]; - throw new RuntimeException("Creation support for Current User Atom not complete"); + + // Initialise to empty + docFinalVersion = 0x03f4; + docMajorNo = 3; + docMinorNo = 0; + releaseVersion = 8; + currentEditOffset = 0; + lastEditUser = "Apache POI"; } /** @@ -130,12 +138,20 @@ public class CurrentUserAtom * Actually do the creation from a block of bytes */ 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"); + } + // Grab the edit offset currentEditOffset = LittleEndian.getUInt(_contents,16); // Grab the versions - docFinalVersionA = LittleEndian.getUShort(_contents,20); - docFinalVersionB = LittleEndian.getUShort(_contents,22); + docFinalVersion = LittleEndian.getUShort(_contents,22); docMajorNo = _contents[24]; docMinorNo = _contents[25]; @@ -194,15 +210,22 @@ public class CurrentUserAtom // Now we have the size of the details, which is 20 LittleEndian.putInt(_contents,8,20); - // Now the ppt magic number (4 bytes) - System.arraycopy(magicNumber,0,_contents,12,4); + // Now the ppt un-encrypted header token (4 bytes) + System.arraycopy(headerToken,0,_contents,12,4); // Now the current edit offset LittleEndian.putInt(_contents,16,(int)currentEditOffset); - // Now the file versions, 2+2+1+1 - LittleEndian.putShort(_contents,20,(short)docFinalVersionA); - LittleEndian.putShort(_contents,22,(short)docFinalVersionB); + // The username gets stored twice, once as US + // ascii, and again as unicode laster on + byte[] asciiUN = new byte[lastEditUser.length()]; + StringUtil.putCompressedUnicode(lastEditUser,asciiUN,0); + + // Now we're able to do the length of the last edited user + LittleEndian.putShort(_contents,20,(short)asciiUN.length); + + // Now the file versions, 2+1+1 + LittleEndian.putShort(_contents,22,(short)docFinalVersion); _contents[24] = docMajorNo; _contents[25] = docMinorNo; @@ -210,9 +233,7 @@ public class CurrentUserAtom _contents[26] = 0; _contents[27] = 0; - // username in bytes in us ascii - byte[] asciiUN = new byte[lastEditUser.length()]; - StringUtil.putCompressedUnicode(lastEditUser,asciiUN,0); + // At this point we have the username as us ascii System.arraycopy(asciiUN,0,_contents,28,asciiUN.length); // 4 byte release version diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCurrentUserAtom.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCurrentUserAtom.java new file mode 100644 index 0000000000..e92998eabf --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCurrentUserAtom.java @@ -0,0 +1,115 @@ + +/* ==================================================================== + 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 junit.framework.TestCase; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.imageio.stream.FileImageInputStream; + +import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException; +import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException; +import org.apache.poi.poifs.filesystem.DocumentEntry; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; + +/** + * Tests that CurrentUserAtom works properly. + * + * @author Nick Burch (nick at torchbox dot com) + */ +public class TestCurrentUserAtom extends TestCase { + /** Not encrypted */ + private String normalFile; + /** Encrypted */ + private String encFile; + + protected void setUp() throws Exception { + super.setUp(); + + String dirname = System.getProperty("HSLF.testdata.path"); + normalFile = dirname + "/basic_test_ppt_file.ppt"; + encFile = dirname + "/Password_Protected-hello.ppt"; + } + + public void testReadNormal() throws Exception { + POIFSFileSystem fs = new POIFSFileSystem( + new FileInputStream(normalFile) + ); + + CurrentUserAtom cu = new CurrentUserAtom(fs); + + // Check the contents + assertEquals("Hogwarts", cu.getLastEditUsername()); + assertEquals(0x2942, cu.getCurrentEditOffset()); + + // Round trip + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + cu.writeOut(baos); + + CurrentUserAtom cu2 = new CurrentUserAtom(baos.toByteArray()); + assertEquals("Hogwarts", cu2.getLastEditUsername()); + assertEquals(0x2942, cu2.getCurrentEditOffset()); + } + + public void testReadEnc() throws Exception { + POIFSFileSystem fs = new POIFSFileSystem( + new FileInputStream(encFile) + ); + + try { + new CurrentUserAtom(fs); + fail(); + } catch(EncryptedPowerPointFileException e) { + // Good + } + } + + public void testWriteNormal() throws Exception { + // Get raw contents from a known file + POIFSFileSystem fs = new POIFSFileSystem( + new FileInputStream(normalFile) + ); + DocumentEntry docProps = (DocumentEntry)fs.getRoot().getEntry("Current User"); + byte[] contents = new byte[docProps.getSize()]; + InputStream in = fs.getRoot().createDocumentInputStream("Current User"); + in.read(contents); + + // Now build up a new one + CurrentUserAtom cu = new CurrentUserAtom(); + cu.setLastEditUsername("Hogwarts"); + cu.setCurrentEditOffset(0x2942); + + // Check it matches + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + cu.writeOut(baos); + byte[] out = baos.toByteArray(); + + assertEquals(contents.length, out.length); + for(int i=0; i