123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
-
- /* ====================================================================
- 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.apache.logging.log4j.util.Unbox.box;
- import static org.apache.poi.hslf.record.RecordAtom.getMaxRecordLength;
- import static org.apache.poi.hslf.usermodel.HSLFSlideShow.PP95_DOCUMENT;
-
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
-
- import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
- import org.apache.logging.log4j.LogManager;
- import org.apache.logging.log4j.Logger;
- 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.Entry;
- import org.apache.poi.poifs.filesystem.POIFSFileSystem;
- import org.apache.poi.util.IOUtils;
- import org.apache.poi.util.LittleEndian;
- 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 static final Logger LOG = LogManager.getLogger(CurrentUserAtom.class);
-
- /** Standard Atom header */
- private static final byte[] atomHeader = new byte[] { 0, 0, -10, 15 };
- /** The PowerPoint magic number for a non-encrypted file */
- private static final byte[] headerToken = new byte[] { 95, -64, -111, -29 };
- /** The PowerPoint magic number for an encrypted file */
- private static final byte[] encHeaderToken = new byte[] { -33, -60, -47, -13 };
- // The Powerpoint 97 version, major and minor numbers
- // 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
- final Entry entry = dir.getEntry("Current User");
- if (!(entry instanceof DocumentEntry)) {
- throw new IllegalArgumentException("Had unexpected type of entry for name: Current User: " + entry.getClass());
- }
- DocumentEntry docProps = (DocumentEntry) entry;
-
- // 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
- try (InputStream in = dir.createDocumentInputStream("Current User")) {
- _contents = IOUtils.toByteArray(in, docProps.getSize(), getMaxRecordLength());
- }
-
- // 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(PP95_DOCUMENT);
- // 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
- LOG.atWarn().log("Invalid username length {} found, treating as if there was no username set", box(usernameLen));
- 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;
-
- if(_contents.length >= start+2*usernameLen) {
- lastEditUser = StringUtil.getFromUnicodeLE(_contents, start, (int)usernameLen);
- } else {
- // Fake from the 8 bit version
- lastEditUser = StringUtil.getFromCompressedUnicode(_contents, 28, (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 = IOUtils.safelyAllocate(size, getMaxRecordLength());
-
- // 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 = IOUtils.safelyAllocate(lastEditUser.length(), getMaxRecordLength());
- 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 = IOUtils.safelyAllocate(lastEditUser.length() * 2L, getMaxRecordLength());
- 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
- try (UnsynchronizedByteArrayOutputStream baos = UnsynchronizedByteArrayOutputStream.builder().get()) {
- writeOut(baos);
- try (InputStream is = baos.toInputStream()) {
- // Write out
- fs.createOrUpdateDocument(is, "Current User");
- }
- }
- }
- }
|