| |
| /* ==================================================================== |
| 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 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.NPOIFSFileSystem; |
| import org.apache.poi.util.LittleEndian; |
| import org.apache.poi.util.POILogFactory; |
| import org.apache.poi.util.POILogger; |
| import org.apache.poi.util.StringUtil; |
| |
| /** |
| * This is a special kind of Atom, because it doesn't live inside the |
| * PowerPoint document. Instead, it lives in a separate stream in the |
| * document. As such, it has to be treated specially |
| */ |
| public class CurrentUserAtom |
| { |
| private final static POILogger logger = POILogFactory.getLogger(CurrentUserAtom.class); |
| |
| /** Standard Atom header */ |
| 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 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 }; |
| |
| /** The version, major and minor numbers */ |
| private int docFinalVersion; |
| private byte docMajorNo; |
| private byte docMinorNo; |
| |
| /** The Offset into the file for the current edit */ |
| private long currentEditOffset; |
| /** The Username of the last person to edit the file */ |
| private String lastEditUser; |
| /** The document release version. Almost always 8 */ |
| private long releaseVersion; |
| |
| /** 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 *********************** */ |
| |
| public int getDocFinalVersion() { return docFinalVersion; } |
| public byte getDocMajorNo() { return docMajorNo; } |
| public byte getDocMinorNo() { return docMinorNo; } |
| |
| public long getReleaseVersion() { return releaseVersion; } |
| public void setReleaseVersion(long rv) { releaseVersion = rv; } |
| |
| /** Points to the UserEditAtom */ |
| public long getCurrentEditOffset() { return currentEditOffset; } |
| public void setCurrentEditOffset(long id ) { currentEditOffset = id; } |
| |
| 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 *************************** */ |
| |
| /** |
| * Create a new Current User Atom |
| */ |
| public CurrentUserAtom() { |
| _contents = new byte[0]; |
| |
| // Initialise to empty |
| docFinalVersion = 0x03f4; |
| docMajorNo = 3; |
| docMinorNo = 0; |
| releaseVersion = 8; |
| currentEditOffset = 0; |
| lastEditUser = "Apache POI"; |
| isEncrypted = false; |
| } |
| |
| |
| /** |
| * Find the Current User in the filesystem, and create from that |
| */ |
| public CurrentUserAtom(DirectoryNode dir) throws IOException { |
| // Decide how big it is |
| DocumentEntry docProps = |
| (DocumentEntry)dir.getEntry("Current User"); |
| |
| // If it's clearly junk, bail out |
| if(docProps.getSize() > 131072) { |
| throw new CorruptPowerPointFileException("The Current User stream is implausably long. It's normally 28-200 bytes long, but was " + docProps.getSize() + " bytes"); |
| } |
| |
| // Grab the contents |
| int len = docProps.getSize(); |
| _contents = new byte[len]; |
| InputStream in = dir.createDocumentInputStream("Current User"); |
| int readLen = in.read(_contents); |
| in.close(); |
| |
| if (len != readLen) { |
| throw new IOException("Current User input stream ended prematurely - expected "+len+" bytes - received "+readLen+" bytes"); |
| } |
| |
| |
| // See how long it is. If it's under 28 bytes long, we can't |
| // read it |
| if(_contents.length < 28) { |
| boolean isPP95 = dir.hasEntry("PP40"); |
| // PPT95 has 4 byte size, then data |
| if (!isPP95 && _contents.length >= 4) { |
| int size = LittleEndian.getInt(_contents); |
| isPP95 = (size + 4 == _contents.length); |
| } |
| |
| if (isPP95) { |
| throw new OldPowerPointFormatException("Based on the Current User stream, you seem to have supplied a PowerPoint95 file, which isn't supported"); |
| } else { |
| throw new CorruptPowerPointFileException("The Current User stream must be at least 28 bytes long, but was only " + _contents.length); |
| } |
| } |
| |
| // Set everything up |
| init(); |
| } |
| |
| /** |
| * 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 |
| |
| isEncrypted = (LittleEndian.getInt(encHeaderToken) == LittleEndian.getInt(_contents,12)); |
| |
| // Grab the edit offset |
| currentEditOffset = LittleEndian.getUInt(_contents,16); |
| |
| // Grab the versions |
| docFinalVersion = LittleEndian.getUShort(_contents,22); |
| docMajorNo = _contents[24]; |
| docMinorNo = _contents[25]; |
| |
| // Get the username length |
| long usernameLen = LittleEndian.getUShort(_contents,20); |
| if(usernameLen > 512) { |
| // Handle the case of it being garbage |
| logger.log(POILogger.WARN, "Warning - invalid username length " + usernameLen + " found, treating as if there was no username set"); |
| usernameLen = 0; |
| } |
| |
| // Now we know the length of the username, |
| // use this to grab the revision |
| if(_contents.length >= 28+(int)usernameLen + 4) { |
| releaseVersion = LittleEndian.getUInt(_contents,28+(int)usernameLen); |
| } else { |
| // No revision given, as not enough data. Odd |
| releaseVersion = 0; |
| } |
| |
| // Grab the unicode username, if stored |
| int start = 28+(int)usernameLen+4; |
| int len = 2*(int)usernameLen; |
| |
| if(_contents.length >= start+len) { |
| byte[] textBytes = new byte[len]; |
| System.arraycopy(_contents,start,textBytes,0,len); |
| lastEditUser = StringUtil.getFromUnicodeLE(textBytes); |
| } else { |
| // Fake from the 8 bit version |
| byte[] textBytes = new byte[(int)usernameLen]; |
| System.arraycopy(_contents,28,textBytes,0,(int)usernameLen); |
| lastEditUser = StringUtil.getFromCompressedUnicode(textBytes,0,(int)usernameLen); |
| } |
| } |
| |
| |
| /** |
| * Writes ourselves back out |
| */ |
| public void writeOut(OutputStream out) throws IOException { |
| // Decide on the size |
| // 8 = atom header |
| // 20 = up to name |
| // 4 = revision |
| // 3 * len = ascii + unicode |
| int size = 8 + 20 + 4 + (3 * lastEditUser.length()); |
| _contents = new byte[size]; |
| |
| // First we have a 8 byte atom header |
| System.arraycopy(atomHeader,0,_contents,0,4); |
| // Size is 20+user len + revision len(4) |
| int atomSize = 20+4+lastEditUser.length(); |
| LittleEndian.putInt(_contents,4,atomSize); |
| |
| // Now we have the size of the details, which is 20 |
| LittleEndian.putInt(_contents,8,20); |
| |
| // Now the ppt un-encrypted header token (4 bytes) |
| System.arraycopy((isEncrypted ? encHeaderToken : headerToken),0,_contents,12,4); |
| |
| // Now the current edit offset |
| LittleEndian.putInt(_contents,16,(int)currentEditOffset); |
| |
| // 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; |
| |
| // 2 bytes blank |
| _contents[26] = 0; |
| _contents[27] = 0; |
| |
| // At this point we have the username as us ascii |
| System.arraycopy(asciiUN,0,_contents,28,asciiUN.length); |
| |
| // 4 byte release version |
| LittleEndian.putInt(_contents,28+asciiUN.length,(int)releaseVersion); |
| |
| // username in unicode |
| byte [] ucUN = new byte[lastEditUser.length()*2]; |
| StringUtil.putUnicodeLE(lastEditUser,ucUN,0); |
| System.arraycopy(ucUN,0,_contents,28+asciiUN.length+4,ucUN.length); |
| |
| // Write out |
| out.write(_contents); |
| } |
| |
| /** |
| * Writes ourselves back out to a filesystem |
| */ |
| public void writeToFS(NPOIFSFileSystem fs) throws IOException { |
| // Grab contents |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| writeOut(baos); |
| ByteArrayInputStream bais = |
| new ByteArrayInputStream(baos.toByteArray()); |
| |
| // Write out |
| fs.createOrUpdateDocument(bais,"Current User"); |
| } |
| } |