292 lines
9.4 KiB
Java
292 lines
9.4 KiB
Java
|
|
/* ====================================================================
|
|
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.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;
|
|
|
|
|
|
/**
|
|
* This is a special kind of Atom, becauase it doesn't live inside the
|
|
* PowerPoint document. Instead, it lives in a seperate stream in the
|
|
* document. As such, it has to be treaded specially
|
|
*
|
|
* @author Nick Burch
|
|
*/
|
|
|
|
public class CurrentUserAtom
|
|
{
|
|
private 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(POIFSFileSystem fs) throws IOException {
|
|
this(fs.getRoot());
|
|
}
|
|
/**
|
|
* 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
|
|
_contents = new byte[docProps.getSize()];
|
|
InputStream in = dir.createDocumentInputStream("Current User");
|
|
in.read(_contents);
|
|
|
|
// See how long it is. If it's under 28 bytes long, we can't
|
|
// read it
|
|
if(_contents.length < 28) {
|
|
if(_contents.length >= 4) {
|
|
// PPT95 has 4 byte size, then data
|
|
int size = LittleEndian.getInt(_contents);
|
|
//System.err.println(size);
|
|
if(size + 4 == _contents.length) {
|
|
throw new OldPowerPointFormatException("Based on the Current User stream, you seem to have supplied a PowerPoint95 file, which isn't supported");
|
|
}
|
|
}
|
|
throw new CorruptPowerPointFileException("The Current User stream must be at least 28 bytes long, but was only " + _contents.length);
|
|
}
|
|
|
|
// Set everything up
|
|
init();
|
|
}
|
|
|
|
/**
|
|
* Create things from the bytes
|
|
*/
|
|
public CurrentUserAtom(byte[] b) {
|
|
_contents = b;
|
|
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(POIFSFileSystem fs) throws IOException {
|
|
// Grab contents
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
writeOut(baos);
|
|
ByteArrayInputStream bais =
|
|
new ByteArrayInputStream(baos.toByteArray());
|
|
|
|
// Write out
|
|
fs.createDocument(bais,"Current User");
|
|
}
|
|
}
|