import java.io.FileOutputStream;\r
import java.io.IOException;\r
import java.io.OutputStreamWriter;\r
-import java.util.Map;\r
+import java.util.Map;
+import java.util.Map.Entry;\r
\r
import org.apache.poi.util.StringUtil;\r
\r
/**\r
* This tool extracts out the source of all VBA Modules of an office file,\r
- * both OOXML (eg XLSM) and OLE2/POIFS (eg DOC), to STDOUT or a directory.\r
+ * both OOXML (eg XLSM) and OLE2/POIFS (eg DOC), to STDOUT or a directory.
+ *
+ * @since 3.15-beta2\r
*/\r
public class VBAMacroExtractor {\r
public static void main(String args[]) throws IOException {\r
output = new File(args[1]);\r
}\r
\r
- VBAMacroExtractor extract = new VBAMacroExtractor();\r
- extract.extract(input, output);\r
+ VBAMacroExtractor extractor = new VBAMacroExtractor();\r
+ extractor.extract(input, output);\r
}\r
- \r
- public void extract(File input, File outputDir) throws IOException {\r
+
+ /**
+ * Extracts the VBA modules from a macro-enabled office file and writes them
+ * to files in <tt>outputDir</tt>.
+ *
+ * Creates the <tt>outputDir</tt>, directory, including any necessary but
+ * nonexistent parent directories, if <tt>outputDir</tt> does not exist.
+ * If <tt>outputDir</tt> is null, writes the contents to standard out instead.
+ *
+ * @param input the macro-enabled office file.
+ * @param outputDir the directory to write the extracted VBA modules to.
+ * @param extension file extension of the extracted VBA modules
+ * @since 3.15-beta2
+ */\r
+ public void extract(File input, File outputDir, String extension) throws IOException {\r
if (! input.exists()) throw new FileNotFoundException(input.toString());\r
System.err.print("Extracting VBA Macros from " + input + " to ");\r
if (outputDir != null) {\r
- if (! outputDir.exists()) outputDir.mkdir();\r
+ if (!outputDir.exists() && !outputDir.mkdirs()) {
+ throw new IOException("Output directory " + outputDir + " could not be created");
+ }\r
System.err.println(outputDir);\r
} else {\r
System.err.println("STDOUT");\r
reader.close();\r
\r
final String divider = "---------------------------------------";\r
- for (String macro : macros.keySet()) {\r
+ for (Entry<String, String> entry : macros.entrySet()) {
+ String moduleName = entry.getKey();
+ String moduleCode = entry.getValue();\r
if (outputDir == null) {\r
System.out.println(divider);\r
- System.out.println(macro);\r
+ System.out.println(moduleName);\r
System.out.println("");\r
- System.out.println(macros.get(macro));\r
+ System.out.println(moduleCode);\r
} else {\r
- File out = new File(outputDir, macro + ".vba");\r
+ File out = new File(outputDir, moduleName + extension);\r
FileOutputStream fout = new FileOutputStream(out);\r
OutputStreamWriter fwriter = new OutputStreamWriter(fout, StringUtil.UTF8);\r
- fwriter.write(macros.get(macro));\r
+ fwriter.write(moduleCode);\r
fwriter.close();\r
fout.close();\r
System.out.println("Extracted " + out);\r
if (outputDir == null) {\r
System.out.println(divider);\r
}\r
+ }
+
+ /**
+ * Extracts the VBA modules from a macro-enabled office file and writes them
+ * to <tt>.vba</tt> files in <tt>outputDir</tt>.
+ *
+ * Creates the <tt>outputDir</tt>, directory, including any necessary but
+ * nonexistent parent directories, if <tt>outputDir</tt> does not exist.
+ * If <tt>outputDir</tt> is null, writes the contents to standard out instead.
+ *
+ * @param input the macro-enabled office file.
+ * @param outputDir the directory to write the extracted VBA modules to.
+ * @since 3.15-beta2
+ */\r
+ public void extract(File input, File outputDir) throws IOException {
+ extract(input, outputDir, ".vba");
}\r
}\r
\r
/**\r
* Finds all VBA Macros in an office file (OLE2/POIFS and OOXML/OPC),\r
- * and returns them.\r
+ * and returns them.
+ *
+ * @since 3.15-beta2\r
*/\r
public class VBAMacroReader implements Closeable {\r
protected static final String VBA_PROJECT_OOXML = "vbaProject.bin";\r
\r
/**\r
* Reads all macros from all modules of the opened office file. \r
- * @return All the macros and their contents\r
+ * @return All the macros and their contents
+ *
+ * @since 3.15-beta2\r
*/\r
public Map<String, String> readMacros() throws IOException {\r
final ModuleMap modules = new ModuleMap();\r
*\r
* @param dir\r
* @param modules\r
- * @throws IOException\r
+ * @throws IOException
+ * @since 3.15-beta2\r
*/\r
protected void findMacros(DirectoryNode dir, ModuleMap modules) throws IOException {\r
if (VBA_PROJECT_POIFS.equalsIgnoreCase(dir.getName())) {\r
byte[] buffer = new byte[length];\r
int count = stream.read(buffer);\r
return new String(buffer, 0, count, charset);\r
+ }
+
+ /**
+ * Skips <tt>n</tt> bytes in an input stream, throwing IOException if the
+ * number of bytes skipped is different than requested.
+ * @throws IOException
+ */
+ private static void trySkip(InputStream in, long n) throws IOException {
+ long skippedBytes = in.skip(n);
+ if (skippedBytes != n) {
+ throw new IOException(
+ "Skipped only " + skippedBytes + " while trying to skip " + n + " bytes. " +
+ " This should never happen.");
+ }
}\r
- \r
+
+ /*
+ * Reads VBA Project modules from a VBA Project directory located at
+ * <tt>macroDir</tt> into <tt>modules</tt>.
+ *
+ * @since 3.15-beta2
+ */ \r
protected void readMacros(DirectoryNode macroDir, ModuleMap modules) throws IOException {\r
for (Entry entry : macroDir) {\r
if (! (entry instanceof DocumentNode)) { continue; }\r
int len = in.readInt();\r
switch (id) {\r
case 0x0009: // PROJECTVERSION\r
- in.skip(6);\r
+ trySkip(in, 6);\r
break;\r
case 0x0003: // PROJECTCODEPAGE\r
int codepage = in.readShort();\r
modules.put(streamName, module);\r
}\r
break;\r
- default:\r
- in.skip(len);\r
+ default:
+ trySkip(in, len);\r
break;\r
}\r
}\r
/**
* Bitmasks for performance
*/
- private static final int[] POWER2 = new int[] { 0x0001, // 0
+ private static final int[] POWER2 = new int[] {
+ 0x0001, // 0
0x0002, // 1
0x0004, // 2
0x0008, // 3
0x1000, // 12
0x2000, // 13
0x4000, // 14
- 0x8000 // 15
+ 0x8000 // 15
};
/** the wrapped inputstream */
- private InputStream in;
+ private final InputStream in;
/** a byte buffer with size 4096 for storing a single chunk */
- private byte[] buf;
+ private final byte[] buf;
/** the current position in the byte buffer for reading */
private int pos;
/**
* Convenience method for read a 2-bytes short in little endian encoding.
*
- * @return
+ * @return short value from the stream
* @throws IOException
*/
public int readShort() throws IOException {
/**
* Convenience method for read a 4-bytes int in little endian encoding.
*
- * @return
+ * @return integer value from the stream
* @throws IOException
*/
public int readInt() throws IOException {