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.

VBAMacroReader.java 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  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.poifs.macros;
  16. import java.io.ByteArrayInputStream;
  17. import java.io.ByteArrayOutputStream;
  18. import java.io.Closeable;
  19. import java.io.File;
  20. import java.io.FileInputStream;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.io.PushbackInputStream;
  24. import java.nio.charset.Charset;
  25. import java.util.HashMap;
  26. import java.util.Map;
  27. import java.util.zip.ZipEntry;
  28. import java.util.zip.ZipInputStream;
  29. import org.apache.poi.poifs.filesystem.DirectoryNode;
  30. import org.apache.poi.poifs.filesystem.DocumentInputStream;
  31. import org.apache.poi.poifs.filesystem.DocumentNode;
  32. import org.apache.poi.poifs.filesystem.Entry;
  33. import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
  34. import org.apache.poi.poifs.filesystem.OfficeXmlFileException;
  35. import org.apache.poi.util.IOUtils;
  36. import org.apache.poi.util.RLEDecompressingInputStream;
  37. /**
  38. * Finds all VBA Macros in an office file (OLE2/POIFS and OOXML/OPC),
  39. * and returns them
  40. */
  41. public class VBAMacroReader implements Closeable {
  42. protected static final String VBA_PROJECT_OOXML = "xl/vbaProject.bin";
  43. protected static final String VBA_PROJECT_POIFS = "VBA";
  44. private NPOIFSFileSystem fs;
  45. public VBAMacroReader(InputStream rstream) throws IOException {
  46. PushbackInputStream stream = new PushbackInputStream(rstream, 8);
  47. byte[] header8 = IOUtils.peekFirst8Bytes(stream);
  48. if (NPOIFSFileSystem.hasPOIFSHeader(header8)) {
  49. fs = new NPOIFSFileSystem(stream);
  50. } else {
  51. openOOXML(stream);
  52. }
  53. }
  54. public VBAMacroReader(File file) throws IOException {
  55. try {
  56. this.fs = new NPOIFSFileSystem(file);
  57. } catch (OfficeXmlFileException e) {
  58. openOOXML(new FileInputStream(file));
  59. }
  60. }
  61. public VBAMacroReader(NPOIFSFileSystem fs) {
  62. this.fs = fs;
  63. }
  64. private void openOOXML(InputStream zipFile) throws IOException {
  65. ZipInputStream zis = new ZipInputStream(zipFile);
  66. ZipEntry zipEntry;
  67. while ((zipEntry = zis.getNextEntry()) != null) {
  68. if (VBA_PROJECT_OOXML.equals(zipEntry.getName())) {
  69. try {
  70. // Make a NPOIFS from the contents, and close the stream
  71. this.fs = new NPOIFSFileSystem(zis);
  72. return;
  73. } catch (IOException e) {
  74. // Tidy up
  75. zis.close();
  76. // Pass on
  77. throw e;
  78. }
  79. }
  80. }
  81. zis.close();
  82. throw new IllegalArgumentException("No VBA project found");
  83. }
  84. public void close() throws IOException {
  85. fs.close();
  86. fs = null;
  87. }
  88. /**
  89. * Reads all macros from all modules of the opened office file.
  90. * @return All the macros and their contents
  91. */
  92. public Map<String, String> readMacros() throws IOException {
  93. final ModuleMap modules = new ModuleMap();
  94. findMacros(fs.getRoot(), modules);
  95. Map<String, String> moduleSources = new HashMap<String, String>();
  96. for (Map.Entry<String, Module> entry : modules.entrySet()) {
  97. Module module = entry.getValue();
  98. if (module.buf != null && module.buf.length > 0) { // Skip empty modules
  99. moduleSources.put(entry.getKey(), new String(module.buf, modules.charset));
  100. }
  101. }
  102. return moduleSources;
  103. }
  104. protected static class Module {
  105. Integer offset;
  106. byte[] buf;
  107. }
  108. protected static class ModuleMap extends HashMap<String, Module> {
  109. Charset charset = Charset.forName("Cp1252"); // default charset
  110. }
  111. protected void findMacros(DirectoryNode dir, ModuleMap modules) throws IOException {
  112. if (VBA_PROJECT_POIFS.equals(dir.getName())) {
  113. // VBA project directory, process
  114. readMacros(dir, modules);
  115. } else {
  116. // Check children
  117. for (Entry child : dir) {
  118. if (child instanceof DirectoryNode) {
  119. findMacros((DirectoryNode)child, modules);
  120. }
  121. }
  122. }
  123. }
  124. protected void readMacros(DirectoryNode macroDir, ModuleMap modules) throws IOException {
  125. for (Entry entry : macroDir) {
  126. if (! (entry instanceof DocumentNode)) { continue; }
  127. String name = entry.getName();
  128. DocumentNode document = (DocumentNode)entry;
  129. DocumentInputStream dis = new DocumentInputStream(document);
  130. if ("dir".equals(name)) {
  131. // process DIR
  132. RLEDecompressingInputStream in = new RLEDecompressingInputStream(dis);
  133. String streamName = null;
  134. while (true) {
  135. int id = in.readShort();
  136. if (id == -1 || id == 0x0010) {
  137. break; // EOF or TERMINATOR
  138. }
  139. int len = in.readInt();
  140. switch (id) {
  141. case 0x0009: // PROJECTVERSION
  142. in.skip(6);
  143. break;
  144. case 0x0003: // PROJECTCODEPAGE
  145. int codepage = in.readShort();
  146. modules.charset = Charset.forName("Cp" + codepage);
  147. break;
  148. case 0x001A: // STREAMNAME
  149. byte[] streamNameBuf = new byte[len];
  150. int count = in.read(streamNameBuf);
  151. streamName = new String(streamNameBuf, 0, count, modules.charset);
  152. break;
  153. case 0x0031: // MODULEOFFSET
  154. int moduleOffset = in.readInt();
  155. Module module = modules.get(streamName);
  156. if (module != null) {
  157. ByteArrayOutputStream out = new ByteArrayOutputStream();
  158. RLEDecompressingInputStream stream = new RLEDecompressingInputStream(new ByteArrayInputStream(
  159. module.buf, moduleOffset, module.buf.length - moduleOffset));
  160. IOUtils.copy(stream, out);
  161. stream.close();
  162. out.close();
  163. module.buf = out.toByteArray();
  164. } else {
  165. module = new Module();
  166. module.offset = moduleOffset;
  167. modules.put(streamName, module);
  168. }
  169. break;
  170. default:
  171. in.skip(len);
  172. break;
  173. }
  174. }
  175. in.close();
  176. } else if (!name.startsWith("__SRP") && !name.startsWith("_VBA_PROJECT")) {
  177. // process module, skip __SRP and _VBA_PROJECT since these do not contain macros
  178. Module module = modules.get(name);
  179. final InputStream in;
  180. // TODO Refactor this to fetch dir then do the rest
  181. if (module == null) {
  182. // no DIR stream with offsets yet, so store the compressed bytes for later
  183. module = new Module();
  184. modules.put(name, module);
  185. in = dis;
  186. } else {
  187. // we know the offset already, so decompress immediately on-the-fly
  188. dis.skip(module.offset);
  189. in = new RLEDecompressingInputStream(dis);
  190. }
  191. final ByteArrayOutputStream out = new ByteArrayOutputStream();
  192. IOUtils.copy(in, out);
  193. in.close();
  194. out.close();
  195. module.buf = out.toByteArray();
  196. }
  197. }
  198. }
  199. }