You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by ni...@apache.org on 2016/04/10 14:45:53 UTC

svn commit: r1738427 - in /poi/trunk: src/java/org/apache/poi/poifs/macros/ src/testcases/org/apache/poi/poifs/ src/testcases/org/apache/poi/poifs/macros/ test-data/spreadsheet/

Author: nick
Date: Sun Apr 10 12:45:53 2016
New Revision: 1738427

URL: http://svn.apache.org/viewvc?rev=1738427&view=rev
Log:
Unit test for VBA macro reading #52949

Added:
    poi/trunk/src/testcases/org/apache/poi/poifs/macros/
    poi/trunk/src/testcases/org/apache/poi/poifs/macros/TestVBAMacroReader.java
    poi/trunk/test-data/spreadsheet/SimpleMacro.vba   (with props)
    poi/trunk/test-data/spreadsheet/SimpleMacro.xls   (with props)
    poi/trunk/test-data/spreadsheet/SimpleMacro.xlsm   (with props)
Modified:
    poi/trunk/src/java/org/apache/poi/poifs/macros/VBAMacroReader.java
    poi/trunk/src/testcases/org/apache/poi/poifs/AllPOIFSTests.java

Modified: poi/trunk/src/java/org/apache/poi/poifs/macros/VBAMacroReader.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/macros/VBAMacroReader.java?rev=1738427&r1=1738426&r2=1738427&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/macros/VBAMacroReader.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/macros/VBAMacroReader.java Sun Apr 10 12:45:53 2016
@@ -19,6 +19,7 @@ package org.apache.poi.poifs.macros;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -30,10 +31,10 @@ import java.util.Map;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
-import org.apache.poi.poifs.eventfilesystem.POIFSReader;
-import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent;
-import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
 import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.poifs.filesystem.DocumentNode;
+import org.apache.poi.poifs.filesystem.Entry;
 import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
 import org.apache.poi.poifs.filesystem.OfficeXmlFileException;
 import org.apache.poi.util.IOUtils;
@@ -43,8 +44,9 @@ import org.apache.poi.util.RLEDecompress
  * Finds all VBA Macros in an office file (OLE2/POIFS and OOXML/OPC),
  *  and returns them
  */
-public class VBAMacroReader {
-    protected static final String VBA_PROJECT = "xl/vbaProject.bin";
+public class VBAMacroReader implements Closeable {
+    protected static final String VBA_PROJECT_OOXML = "xl/vbaProject.bin";
+    protected static final String VBA_PROJECT_POIFS = "VBA";
     
     private NPOIFSFileSystem fs;
     
@@ -55,7 +57,6 @@ public class VBAMacroReader {
         if (NPOIFSFileSystem.hasPOIFSHeader(header8)) {
             fs = new NPOIFSFileSystem(stream);
         } else {
-            stream.unread(header8);
             openOOXML(stream);
         }
     }
@@ -75,125 +76,142 @@ public class VBAMacroReader {
         ZipInputStream zis = new ZipInputStream(zipFile);
         ZipEntry zipEntry;
         while ((zipEntry = zis.getNextEntry()) != null) {
-            if (VBA_PROJECT.equals(zipEntry.getName())) {
+            if (VBA_PROJECT_OOXML.equals(zipEntry.getName())) {
                 try {
+                    // Make a NPOIFS from the contents, and close the stream
                     this.fs = new NPOIFSFileSystem(zis);
-                } finally {
-                    zis.closeEntry();
+                    return;
+                } catch (IOException e) {
+                    // Tidy up
+                    zis.close();
+                    
+                    // Pass on
+                    throw e;
                 }
-                zis.close();
-                return;
             }
         }
         zis.close();
         throw new IllegalArgumentException("No VBA project found");
     }
+    
+    public void close() throws IOException {
+        fs.close();
+        fs = null;
+    }
 
     /**
      * Reads all macros from all modules of the opened office file. 
+     * @return All the macros and their contents
      */
     public Map<String, String> readMacros() throws IOException {
-        class Module {
-            Integer offset;
-            byte[] buf;
+        final ModuleMap modules = new ModuleMap();
+        findMacros(fs.getRoot(), modules);
+        
+        Map<String, String> moduleSources = new HashMap<String, String>();
+        for (Map.Entry<String, Module> entry : modules.entrySet()) {
+            Module module = entry.getValue();
+            if (module.buf != null && module.buf.length > 0) { // Skip empty modules
+                moduleSources.put(entry.getKey(), new String(module.buf, modules.charset));
+            }
         }
-        class ModuleMap extends HashMap<String, Module> {
-
-            Charset charset = Charset.forName("Cp1252"); // default charset
+        return moduleSources;
+    }
+    
+    protected static class Module {
+        Integer offset;
+        byte[] buf;
+    }
+    protected static class ModuleMap extends HashMap<String, Module> {
+        Charset charset = Charset.forName("Cp1252"); // default charset
+    }
+    
+    protected void findMacros(DirectoryNode dir, ModuleMap modules) throws IOException {
+        if (VBA_PROJECT_POIFS.equals(dir.getName())) {
+            // VBA project directory, process
+            readMacros(dir, modules);
+        } else {
+            // Check children
+            for (Entry child : dir) {
+                if (child instanceof DirectoryNode) {
+                    findMacros((DirectoryNode)child, modules);
+                }
+            }
         }
-        try {
-            final ModuleMap modules = new ModuleMap();
-            POIFSReader dirReader = new POIFSReader();
-            dirReader.registerListener(new POIFSReaderListener() {
-
-                public void processPOIFSReaderEvent(POIFSReaderEvent event) {
-                    try {
-                        String name = event.getName();
-                        if (event.getPath().toString().endsWith("\\VBA")) {
-                            if ("dir".equals(name)) {
-                                // process DIR
-                                RLEDecompressingInputStream in = new RLEDecompressingInputStream(event.getStream());
-                                String streamName = null;
-                                while (true) {
-                                    int id = in.readShort();
-                                    if (id == -1 || id == 0x0010) {
-                                        break; // EOF or TERMINATOR
-                                    }
-                                    int len = in.readInt();
-                                    switch (id) {
-                                        case 0x0009: // PROJECTVERSION
-                                            in.skip(6);
-                                            break;
-                                        case 0x0003: // PROJECTCODEPAGE
-                                            int codepage = in.readShort();
-                                            modules.charset = Charset.forName("Cp" + codepage);
-                                            break;
-                                        case 0x001A: // STREAMNAME
-                                            byte[] streamNameBuf = new byte[len];
-                                            int count = in.read(streamNameBuf);
-                                            streamName = new String(streamNameBuf, 0, count, modules.charset);
-                                            break;
-                                        case 0x0031: // MODULEOFFSET
-                                            int moduleOffset = in.readInt();
-                                            Module module = modules.get(streamName);
-                                            if (module != null) {
-                                                ByteArrayOutputStream out = new ByteArrayOutputStream();
-                                                RLEDecompressingInputStream stream = new RLEDecompressingInputStream(new ByteArrayInputStream(
-                                                        module.buf, moduleOffset, module.buf.length - moduleOffset));
-                                                IOUtils.copy(stream, out);
-                                                stream.close();
-                                                out.close();
-                                                module.buf = out.toByteArray();
-                                            } else {
-                                                module = new Module();
-                                                module.offset = moduleOffset;
-                                                modules.put(streamName, module);
-                                            }
-                                            break;
-                                        default:
-                                            in.skip(len);
-                                            break;
-                                    }
-                                }
-                            } else if (!name.startsWith("__SRP") && !name.startsWith("_VBA_PROJECT")) {
-                                // process module, skip __SRP and _VBA_PROJECT since these do not contain macros
-                                Module module = modules.get(name);
-                                final DocumentInputStream stream = event.getStream();
-                                final InputStream in;
-                                if (module == null) {
-                                    // no DIR stream with offsets yet, so store the compressed bytes for later
-                                    module = new Module();
-                                    modules.put(name, module);
-                                    in = stream;
-                                } else {
-                                    // we know the offset already, so decompress immediately on-the-fly
-                                    stream.skip(module.offset);
-                                    in = new RLEDecompressingInputStream(stream);
-                                }
-                                final ByteArrayOutputStream out = new ByteArrayOutputStream();
-                                IOUtils.copy(in, out);
-                                in.close();
-                                out.close();
-                                module.buf = out.toByteArray();
-                            }
+    }
+    protected void readMacros(DirectoryNode macroDir, ModuleMap modules) throws IOException {
+        for (Entry entry : macroDir) {
+            if (! (entry instanceof DocumentNode)) { continue; }
+            
+            String name = entry.getName();
+            DocumentNode document = (DocumentNode)entry;
+            DocumentInputStream dis = new DocumentInputStream(document);
+            if ("dir".equals(name)) {
+                // process DIR
+                RLEDecompressingInputStream in = new RLEDecompressingInputStream(dis);
+                String streamName = null;
+                while (true) {
+                    int id = in.readShort();
+                    if (id == -1 || id == 0x0010) {
+                        break; // EOF or TERMINATOR
+                    }
+                    int len = in.readInt();
+                    switch (id) {
+                    case 0x0009: // PROJECTVERSION
+                        in.skip(6);
+                        break;
+                    case 0x0003: // PROJECTCODEPAGE
+                        int codepage = in.readShort();
+                        modules.charset = Charset.forName("Cp" + codepage);
+                        break;
+                    case 0x001A: // STREAMNAME
+                        byte[] streamNameBuf = new byte[len];
+                        int count = in.read(streamNameBuf);
+                        streamName = new String(streamNameBuf, 0, count, modules.charset);
+                        break;
+                    case 0x0031: // MODULEOFFSET
+                        int moduleOffset = in.readInt();
+                        Module module = modules.get(streamName);
+                        if (module != null) {
+                            ByteArrayOutputStream out = new ByteArrayOutputStream();
+                            RLEDecompressingInputStream stream = new RLEDecompressingInputStream(new ByteArrayInputStream(
+                                    module.buf, moduleOffset, module.buf.length - moduleOffset));
+                            IOUtils.copy(stream, out);
+                            stream.close();
+                            out.close();
+                            module.buf = out.toByteArray();
+                        } else {
+                            module = new Module();
+                            module.offset = moduleOffset;
+                            modules.put(streamName, module);
                         }
-                    } catch (IOException e) {
-                        throw new RuntimeException(e);
+                        break;
+                    default:
+                        in.skip(len);
+                        break;
                     }
                 }
-            });
-            dirReader.read(null); // TODO
-            Map<String, String> moduleSources = new HashMap<String, String>();
-            for (Map.Entry<String, Module> entry : modules.entrySet()) {
-                Module module = entry.getValue();
-                if (module.buf != null && module.buf.length > 0) { // Skip empty modules
-                    moduleSources.put(entry.getKey(), new String(module.buf, modules.charset));
+                in.close();
+            } else if (!name.startsWith("__SRP") && !name.startsWith("_VBA_PROJECT")) {
+                // process module, skip __SRP and _VBA_PROJECT since these do not contain macros
+                Module module = modules.get(name);
+                final InputStream in;
+                // TODO Refactor this to fetch dir then do the rest
+                if (module == null) {
+                    // no DIR stream with offsets yet, so store the compressed bytes for later
+                    module = new Module();
+                    modules.put(name, module);
+                    in = dis;
+                } else {
+                    // we know the offset already, so decompress immediately on-the-fly
+                    dis.skip(module.offset);
+                    in = new RLEDecompressingInputStream(dis);
                 }
+                final ByteArrayOutputStream out = new ByteArrayOutputStream();
+                IOUtils.copy(in, out);
+                in.close();
+                out.close();
+                module.buf = out.toByteArray();
             }
-            return moduleSources;
-        } catch (IOException e) {
-            e.printStackTrace();
-            throw e;
         }
     }
 }

Modified: poi/trunk/src/testcases/org/apache/poi/poifs/AllPOIFSTests.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/poifs/AllPOIFSTests.java?rev=1738427&r1=1738426&r2=1738427&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/poifs/AllPOIFSTests.java (original)
+++ poi/trunk/src/testcases/org/apache/poi/poifs/AllPOIFSTests.java Sun Apr 10 12:45:53 2016
@@ -19,6 +19,7 @@ package org.apache.poi.poifs;
 
 import org.apache.poi.poifs.eventfilesystem.TestPOIFSReaderRegistry;
 import org.apache.poi.poifs.filesystem.AllPOIFSFileSystemTests;
+import org.apache.poi.poifs.macros.TestVBAMacroReader;
 import org.apache.poi.poifs.nio.TestDataSource;
 import org.apache.poi.poifs.property.AllPOIFSPropertyTests;
 import org.apache.poi.poifs.storage.AllPOIFSStorageTests;
@@ -32,6 +33,7 @@ import org.junit.runners.Suite;
 @Suite.SuiteClasses({
       TestPOIFSReaderRegistry.class
     , TestDataSource.class
+    , TestVBAMacroReader.class
     , AllPOIFSFileSystemTests.class
     , AllPOIFSPropertyTests.class
     , AllPOIFSStorageTests.class

Added: poi/trunk/src/testcases/org/apache/poi/poifs/macros/TestVBAMacroReader.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/poifs/macros/TestVBAMacroReader.java?rev=1738427&view=auto
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/poifs/macros/TestVBAMacroReader.java (added)
+++ poi/trunk/src/testcases/org/apache/poi/poifs/macros/TestVBAMacroReader.java Sun Apr 10 12:45:53 2016
@@ -0,0 +1,112 @@
+/* ====================================================================
+   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.poifs.macros;
+
+import static org.apache.poi.POITestCase.assertContains;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.Map;
+
+import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.StringUtil;
+import org.junit.Test;
+
+public class TestVBAMacroReader {
+    private final String testMacroContents;
+    private final String testMacroNoSub;
+    public TestVBAMacroReader() throws Exception {
+        File macro = HSSFTestDataSamples.getSampleFile("SimpleMacro.vba");
+        testMacroContents = new String(
+                IOUtils.toByteArray(new FileInputStream(macro)),
+                StringUtil.UTF8
+        );
+        
+        if (! testMacroContents.startsWith("Sub ")) {
+            throw new IllegalArgumentException("Not a macro");
+        }
+        testMacroNoSub = testMacroContents.substring(testMacroContents.indexOf("()")+3);
+    }
+    
+    @Test
+    public void fromStream() throws Exception {
+        VBAMacroReader r;
+        
+        r = new VBAMacroReader(HSSFTestDataSamples.openSampleFileStream("SimpleMacro.xls"));
+        assertMacroContents(r);
+        r.close();
+        
+        r = new VBAMacroReader(HSSFTestDataSamples.openSampleFileStream("SimpleMacro.xlsm"));
+        assertMacroContents(r);
+        r.close();
+    }
+    @Test
+    public void fromFile() throws Exception {
+        VBAMacroReader r;
+        
+        r = new VBAMacroReader(HSSFTestDataSamples.getSampleFile("SimpleMacro.xls"));
+        assertMacroContents(r);
+        r.close();
+        
+        r = new VBAMacroReader(HSSFTestDataSamples.getSampleFile("SimpleMacro.xlsm"));
+        assertMacroContents(r);
+        r.close();
+    }
+    @Test
+    public void fromNPOIFS() throws Exception {
+        NPOIFSFileSystem fs = new NPOIFSFileSystem(
+                HSSFTestDataSamples.getSampleFile("SimpleMacro.xls"));
+        VBAMacroReader r = new VBAMacroReader(fs);
+        assertMacroContents(r);
+        r.close();
+    }
+    
+    protected void assertMacroContents(VBAMacroReader r) throws Exception {
+        Map<String,String> contents = r.readMacros();
+        
+        assertFalse(contents.isEmpty());
+        assertEquals(5, contents.size());
+        
+        // Check the ones without scripts
+        String[] noScripts = new String[] { "ThisWorkbook",
+                "Sheet1", "Sheet2", "Sheet3" };
+        for (String entry : noScripts) {
+            assertTrue(entry, contents.containsKey(entry));
+            
+            String content = contents.get(entry);
+            assertContains(content, "Attribute VB_Exposed = True");
+            assertContains(content, "Attribute VB_Customizable = True");
+            assertContains(content, "Attribute VB_TemplateDerived = False");
+            assertContains(content, "Attribute VB_GlobalNameSpace = False");
+            assertContains(content, "Attribute VB_Exposed = True");
+        }
+        
+        // Check the script one
+        String content = contents.get("Module1");
+        assertContains(content, "Attribute VB_Name = \"Module1\"");
+        assertContains(content, "Attribute TestMacro.VB_Description = \"This is a test macro\"");
+
+        // And the macro itself
+        assertContains(content, testMacroNoSub);
+    }
+}

Added: poi/trunk/test-data/spreadsheet/SimpleMacro.vba
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/SimpleMacro.vba?rev=1738427&view=auto
==============================================================================
--- poi/trunk/test-data/spreadsheet/SimpleMacro.vba (added)
+++ poi/trunk/test-data/spreadsheet/SimpleMacro.vba Sun Apr 10 12:45:53 2016
@@ -0,0 +1,10 @@
+Sub TestMacro()
+'
+' TestMacro Macro
+' This is a test macro
+'
+
+'
+    ActiveCell.FormulaR1C1 = "This is a macro workbook"
+    Range("A2").Select
+End Sub

Propchange: poi/trunk/test-data/spreadsheet/SimpleMacro.vba
------------------------------------------------------------------------------
    svn:eol-style = CRLF

Added: poi/trunk/test-data/spreadsheet/SimpleMacro.xls
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/SimpleMacro.xls?rev=1738427&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/test-data/spreadsheet/SimpleMacro.xls
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: poi/trunk/test-data/spreadsheet/SimpleMacro.xlsm
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/SimpleMacro.xlsm?rev=1738427&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/test-data/spreadsheet/SimpleMacro.xlsm
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org