You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CurrentUserAtom.java 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.hslf.record;
  16. import static org.apache.poi.hslf.usermodel.HSLFSlideShow.PP95_DOCUMENT;
  17. import java.io.ByteArrayInputStream;
  18. import java.io.ByteArrayOutputStream;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.OutputStream;
  22. import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
  23. import org.apache.poi.hslf.exceptions.OldPowerPointFormatException;
  24. import org.apache.poi.poifs.filesystem.DirectoryNode;
  25. import org.apache.poi.poifs.filesystem.DocumentEntry;
  26. import org.apache.poi.poifs.filesystem.POIFSFileSystem;
  27. import org.apache.poi.util.IOUtils;
  28. import org.apache.poi.util.LittleEndian;
  29. import org.apache.poi.util.POILogFactory;
  30. import org.apache.poi.util.POILogger;
  31. import org.apache.poi.util.StringUtil;
  32. /**
  33. * This is a special kind of Atom, because it doesn't live inside the
  34. * PowerPoint document. Instead, it lives in a separate stream in the
  35. * document. As such, it has to be treated specially
  36. */
  37. public class CurrentUserAtom
  38. {
  39. private final static POILogger logger = POILogFactory.getLogger(CurrentUserAtom.class);
  40. //arbitrarily selected; may need to increase
  41. private static final int MAX_RECORD_LENGTH = 1_000_000;
  42. /** Standard Atom header */
  43. private static final byte[] atomHeader = new byte[] { 0, 0, -10, 15 };
  44. /** The PowerPoint magic number for a non-encrypted file */
  45. private static final byte[] headerToken = new byte[] { 95, -64, -111, -29 };
  46. /** The PowerPoint magic number for an encrypted file */
  47. private static final byte[] encHeaderToken = new byte[] { -33, -60, -47, -13 };
  48. // The Powerpoint 97 version, major and minor numbers
  49. // byte[] ppt97FileVer = new byte[] { 8, 00, -13, 03, 03, 00 };
  50. /** The version, major and minor numbers */
  51. private int docFinalVersion;
  52. private byte docMajorNo;
  53. private byte docMinorNo;
  54. /** The Offset into the file for the current edit */
  55. private long currentEditOffset;
  56. /** The Username of the last person to edit the file */
  57. private String lastEditUser;
  58. /** The document release version. Almost always 8 */
  59. private long releaseVersion;
  60. /** Only correct after reading in or writing out */
  61. private byte[] _contents;
  62. /** Flag for encryption state of the whole file */
  63. private boolean isEncrypted;
  64. /* ********************* getter/setter follows *********************** */
  65. public int getDocFinalVersion() { return docFinalVersion; }
  66. public byte getDocMajorNo() { return docMajorNo; }
  67. public byte getDocMinorNo() { return docMinorNo; }
  68. public long getReleaseVersion() { return releaseVersion; }
  69. public void setReleaseVersion(long rv) { releaseVersion = rv; }
  70. /** Points to the UserEditAtom */
  71. public long getCurrentEditOffset() { return currentEditOffset; }
  72. public void setCurrentEditOffset(long id ) { currentEditOffset = id; }
  73. public String getLastEditUsername() { return lastEditUser; }
  74. public void setLastEditUsername(String u) { lastEditUser = u; }
  75. public boolean isEncrypted() { return isEncrypted; }
  76. public void setEncrypted(boolean isEncrypted) { this.isEncrypted = isEncrypted; }
  77. /* ********************* real code follows *************************** */
  78. /**
  79. * Create a new Current User Atom
  80. */
  81. public CurrentUserAtom() {
  82. _contents = new byte[0];
  83. // Initialise to empty
  84. docFinalVersion = 0x03f4;
  85. docMajorNo = 3;
  86. docMinorNo = 0;
  87. releaseVersion = 8;
  88. currentEditOffset = 0;
  89. lastEditUser = "Apache POI";
  90. isEncrypted = false;
  91. }
  92. /**
  93. * Find the Current User in the filesystem, and create from that
  94. */
  95. public CurrentUserAtom(DirectoryNode dir) throws IOException {
  96. // Decide how big it is
  97. DocumentEntry docProps =
  98. (DocumentEntry)dir.getEntry("Current User");
  99. // If it's clearly junk, bail out
  100. if(docProps.getSize() > 131072) {
  101. throw new CorruptPowerPointFileException("The Current User stream is implausably long. It's normally 28-200 bytes long, but was " + docProps.getSize() + " bytes");
  102. }
  103. // Grab the contents
  104. try (InputStream in = dir.createDocumentInputStream("Current User")) {
  105. _contents = IOUtils.toByteArray(in, docProps.getSize(), MAX_RECORD_LENGTH);
  106. }
  107. // See how long it is. If it's under 28 bytes long, we can't
  108. // read it
  109. if(_contents.length < 28) {
  110. boolean isPP95 = dir.hasEntry(PP95_DOCUMENT);
  111. // PPT95 has 4 byte size, then data
  112. if (!isPP95 && _contents.length >= 4) {
  113. int size = LittleEndian.getInt(_contents);
  114. isPP95 = (size + 4 == _contents.length);
  115. }
  116. if (isPP95) {
  117. throw new OldPowerPointFormatException("Based on the Current User stream, you seem to have supplied a PowerPoint95 file, which isn't supported");
  118. } else {
  119. throw new CorruptPowerPointFileException("The Current User stream must be at least 28 bytes long, but was only " + _contents.length);
  120. }
  121. }
  122. // Set everything up
  123. init();
  124. }
  125. /**
  126. * Actually do the creation from a block of bytes
  127. */
  128. private void init() {
  129. // First up is the size, in 4 bytes, which is fixed
  130. // Then is the header
  131. isEncrypted = (LittleEndian.getInt(encHeaderToken) == LittleEndian.getInt(_contents,12));
  132. // Grab the edit offset
  133. currentEditOffset = LittleEndian.getUInt(_contents,16);
  134. // Grab the versions
  135. docFinalVersion = LittleEndian.getUShort(_contents,22);
  136. docMajorNo = _contents[24];
  137. docMinorNo = _contents[25];
  138. // Get the username length
  139. long usernameLen = LittleEndian.getUShort(_contents,20);
  140. if(usernameLen > 512) {
  141. // Handle the case of it being garbage
  142. logger.log(POILogger.WARN, "Warning - invalid username length " + usernameLen + " found, treating as if there was no username set");
  143. usernameLen = 0;
  144. }
  145. // Now we know the length of the username,
  146. // use this to grab the revision
  147. if(_contents.length >= 28+(int)usernameLen + 4) {
  148. releaseVersion = LittleEndian.getUInt(_contents,28+(int)usernameLen);
  149. } else {
  150. // No revision given, as not enough data. Odd
  151. releaseVersion = 0;
  152. }
  153. // Grab the unicode username, if stored
  154. int start = 28+(int)usernameLen+4;
  155. int len = 2*(int)usernameLen;
  156. if(_contents.length >= start+len) {
  157. byte[] textBytes = IOUtils.safelyAllocate(len, MAX_RECORD_LENGTH);
  158. System.arraycopy(_contents,start,textBytes,0,len);
  159. lastEditUser = StringUtil.getFromUnicodeLE(textBytes);
  160. } else {
  161. // Fake from the 8 bit version
  162. byte[] textBytes = IOUtils.safelyAllocate(usernameLen, MAX_RECORD_LENGTH);
  163. System.arraycopy(_contents,28,textBytes,0,(int)usernameLen);
  164. lastEditUser = StringUtil.getFromCompressedUnicode(textBytes,0,(int)usernameLen);
  165. }
  166. }
  167. /**
  168. * Writes ourselves back out
  169. */
  170. public void writeOut(OutputStream out) throws IOException {
  171. // Decide on the size
  172. // 8 = atom header
  173. // 20 = up to name
  174. // 4 = revision
  175. // 3 * len = ascii + unicode
  176. int size = 8 + 20 + 4 + (3 * lastEditUser.length());
  177. _contents = IOUtils.safelyAllocate(size, MAX_RECORD_LENGTH);
  178. // First we have a 8 byte atom header
  179. System.arraycopy(atomHeader,0,_contents,0,4);
  180. // Size is 20+user len + revision len(4)
  181. int atomSize = 20+4+lastEditUser.length();
  182. LittleEndian.putInt(_contents,4,atomSize);
  183. // Now we have the size of the details, which is 20
  184. LittleEndian.putInt(_contents,8,20);
  185. // Now the ppt un-encrypted header token (4 bytes)
  186. System.arraycopy((isEncrypted ? encHeaderToken : headerToken),0,_contents,12,4);
  187. // Now the current edit offset
  188. LittleEndian.putInt(_contents,16,(int)currentEditOffset);
  189. // The username gets stored twice, once as US
  190. // ascii, and again as unicode laster on
  191. byte[] asciiUN = IOUtils.safelyAllocate(lastEditUser.length(), MAX_RECORD_LENGTH);
  192. StringUtil.putCompressedUnicode(lastEditUser,asciiUN,0);
  193. // Now we're able to do the length of the last edited user
  194. LittleEndian.putShort(_contents,20,(short)asciiUN.length);
  195. // Now the file versions, 2+1+1
  196. LittleEndian.putShort(_contents,22,(short)docFinalVersion);
  197. _contents[24] = docMajorNo;
  198. _contents[25] = docMinorNo;
  199. // 2 bytes blank
  200. _contents[26] = 0;
  201. _contents[27] = 0;
  202. // At this point we have the username as us ascii
  203. System.arraycopy(asciiUN,0,_contents,28,asciiUN.length);
  204. // 4 byte release version
  205. LittleEndian.putInt(_contents,28+asciiUN.length,(int)releaseVersion);
  206. // username in unicode
  207. byte [] ucUN = IOUtils.safelyAllocate(lastEditUser.length() * 2L, MAX_RECORD_LENGTH);
  208. StringUtil.putUnicodeLE(lastEditUser,ucUN,0);
  209. System.arraycopy(ucUN,0,_contents,28+asciiUN.length+4,ucUN.length);
  210. // Write out
  211. out.write(_contents);
  212. }
  213. /**
  214. * Writes ourselves back out to a filesystem
  215. */
  216. public void writeToFS(POIFSFileSystem fs) throws IOException {
  217. // Grab contents
  218. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  219. writeOut(baos);
  220. ByteArrayInputStream bais =
  221. new ByteArrayInputStream(baos.toByteArray());
  222. // Write out
  223. fs.createOrUpdateDocument(bais,"Current User");
  224. }
  225. }