From f5091846ffac98632ea67aa9cab7d94c4c2bf2a6 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Sun, 10 Apr 2016 12:45:53 +0000 Subject: [PATCH] Unit test for VBA macro reading #52949 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1738427 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/poifs/macros/VBAMacroReader.java | 228 ++++++++++-------- .../org/apache/poi/poifs/AllPOIFSTests.java | 2 + .../poi/poifs/macros/TestVBAMacroReader.java | 112 +++++++++ test-data/spreadsheet/SimpleMacro.vba | 10 + test-data/spreadsheet/SimpleMacro.xls | Bin 0 -> 30720 bytes test-data/spreadsheet/SimpleMacro.xlsm | Bin 0 -> 13796 bytes 6 files changed, 247 insertions(+), 105 deletions(-) create mode 100644 src/testcases/org/apache/poi/poifs/macros/TestVBAMacroReader.java create mode 100644 test-data/spreadsheet/SimpleMacro.vba create mode 100644 test-data/spreadsheet/SimpleMacro.xls create mode 100644 test-data/spreadsheet/SimpleMacro.xlsm diff --git a/src/java/org/apache/poi/poifs/macros/VBAMacroReader.java b/src/java/org/apache/poi/poifs/macros/VBAMacroReader.java index 990219850e..0b8ebdda86 100644 --- a/src/java/org/apache/poi/poifs/macros/VBAMacroReader.java +++ b/src/java/org/apache/poi/poifs/macros/VBAMacroReader.java @@ -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.RLEDecompressingInputStream; * 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 readMacros() throws IOException { - class Module { - Integer offset; - byte[] buf; + final ModuleMap modules = new ModuleMap(); + findMacros(fs.getRoot(), modules); + + Map moduleSources = new HashMap(); + for (Map.Entry 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 { - - Charset charset = Charset.forName("Cp1252"); // default charset + return moduleSources; + } + + protected static class Module { + Integer offset; + byte[] buf; + } + protected static class ModuleMap extends HashMap { + 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 moduleSources = new HashMap(); - for (Map.Entry 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; } } } diff --git a/src/testcases/org/apache/poi/poifs/AllPOIFSTests.java b/src/testcases/org/apache/poi/poifs/AllPOIFSTests.java index 012cc3b644..a50bfe6f43 100644 --- a/src/testcases/org/apache/poi/poifs/AllPOIFSTests.java +++ b/src/testcases/org/apache/poi/poifs/AllPOIFSTests.java @@ -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 diff --git a/src/testcases/org/apache/poi/poifs/macros/TestVBAMacroReader.java b/src/testcases/org/apache/poi/poifs/macros/TestVBAMacroReader.java new file mode 100644 index 0000000000..ad1f8393e2 --- /dev/null +++ b/src/testcases/org/apache/poi/poifs/macros/TestVBAMacroReader.java @@ -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 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); + } +} diff --git a/test-data/spreadsheet/SimpleMacro.vba b/test-data/spreadsheet/SimpleMacro.vba new file mode 100644 index 0000000000..03107cae5d --- /dev/null +++ b/test-data/spreadsheet/SimpleMacro.vba @@ -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 diff --git a/test-data/spreadsheet/SimpleMacro.xls b/test-data/spreadsheet/SimpleMacro.xls new file mode 100644 index 0000000000000000000000000000000000000000..ec116857978b322dbedf19a3fbc69d34d4e54ce2 GIT binary patch literal 30720 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 literal 0 HcmV?d00001 diff --git a/test-data/spreadsheet/SimpleMacro.xlsm b/test-data/spreadsheet/SimpleMacro.xlsm new file mode 100644 index 0000000000000000000000000000000000000000..ec64525bf8fa45bb56e9b50dce90189590323d1a GIT binary patch literal 13796 zcmeHu1y>x~)-CSt?h;%BB)Ej&PH=a332tfJ9U8Y_!3of~LvWYi794_WKXSf%-#I7u zy!QvbTVr(X(W7e3?y5c4-mCVUr6datg98BvfdBykK?y-3;w2FS2>~$=3ju)*fdHi= z?qKg~X76gC_R-PIMUU0f&Xyt{2I^HV1k}s<|K0u<-+>V|dBq-fls4*f@-kiJL`W<# zOHz2Dm;jhZ2)6UNYVPn&_D28MWmK5yg*1^M*=6o??{W9X`@On;Tva*K;Huudqj0D; zwElZ1S~Bu4-&B>A;2b((W0TXU!!Np?^}$AD@klY(YTD9$pX#!>Y#w0&5L~i3!Hl!wYj8ccDRI{8|zYPK5WGh+Z%>~iA*25mxZ96k)IGYYgHAK61zVYpvIPqceAoTd=rZ8vd z1kbQ0(j#4(uF6I!51^FWHjgtK6tonC;w z$rsAJ=J9aU-FaA_-qph3DmG$C8lLQ>j+*?S}(&`G?Q7k# zNsrXV+@y*)$md7SMe*#)!$y{qiG-icz9;Pn0y@Z)SOjLftP$Y7;md^X1@3CUa?3k- zvhDgBrz%8=WMlJxbKem+M=l*-{+uE9O*$C&0yvF1^gv|-olUo-t9S`+ED2FLZDD?y>=a-iAc!CkAU$o_ z{wGm99Gq>89UN?aO?UrJ8pxM9@8#V8-CJu?*Dullevu}LUw*ku#B!e%!HO4V3$k{` zt|y}~q9@luQEj3{+Rmf2Nusv$5llZYX8lxY;|vixOr((bsu(7!zL}G@RI{$3$VmjU8Hn2s$J=b)Zr|Yh^kp+o%V`x|rwI5xxn(gpf6Y`dVQ5P|L+5|~U zLrWqst=1^*0h6Gzu+9>hT)KRbZ6FS8M6VBgg7^qEv6r94X)?ASb|DgJVI}@{7O>4k@CWoVBL6z$g=>FhS(J*N!!iYqZ_W%jz|%t>Xh^FJEE^)KWScH5 z9_4yH&%p?XJP5R2@?Yz-Z<9L|&pDajaO6v!9c`Z6rsu1wVED0}p$t`Auaa&eN`3<} zRR~A#mddLP3~CQ5;XN#8D<5k~wXiWjz#=f1cpKuLUff=hhxxqJXy~Y-H6IpG#9!R* zMp0M=DONBYRU=>*!s%+L*)s09P^Lob)38$ti4BJZuMLxQYBTHAqOUH~Gkr!ywR8=# zRJzep&Hse%p#mERyQ8^;_H5Op3VOp3J>8ci&mc-{$oFK^o^?#O3@b~}u}pKvvWK`< z^R63j*|u@x(fu1whgA!i-Q5Oj%&E-|x`UrGRUQ^S>9yqyNWoPo(@cwuFuzn0=`A>w zIc3{NNpgu*%t^Y+QKy3;BVlN2Ou4>tPv4PjUJz9a!g4TYS1IV24KstFKE*yXM9Jkn* zSt_DynAS41kcl5R4SG^2K?)MxlK$*t>+swx(yAOjM}x7Qk>1+ll-qvLx$Ec|PS-07 z?ZbH;4R_~zxXu~QFY||P-VMNPNW(RF`AX(@8CTDem5Gzg0Y_V$$My}%m7rUwrz_fY zoh*ftNg1;m3DiNMxzsb_S#w|S(Jtb+KHikGt1hk<536)EZwvk@pfIJsn)nlSb4SO2llQwOk}UBY=7{*J5f z^B!^*7K<0Y25)w!uK8W>&65`hXZv0JJjdVh)anJ__t1AdK7M?I!D&f38OfIOczrY! zSta!J1R9_{vkQFx?lw+RB41}4f%FW)l?&iROHb|8j@yHwY5|KD=#IFbjYPU$`NrI= z3C$_mLqp{J6|{cNiias_h;gwc;vTWlHpC5|1u3fxs-yS?qoyZcWU%wD@rPbgLm$n4 zP^8H~Lz@hA7t-Hn?#<|;z)zKtWk)^^km@*WT92QA*l==MOC`JPqf)mxT=kJv8y5+`R? zyuHdQ0MZFQW@F(vRRraz>t@E_Qzu^WOa5EP0j3q-RIGuO68s>3Zbu1in6T{ha4Z9T z`tRXuT|jwu4s|%07S&Q}BCy_~InF`j&e<2+uWfbHxf1apB~Utevk@8-XNOx z{lvTaBGyqpv8rQ`r?K<1Z+_<;1AwuT7;D)kbBtem(Npo23G{1=?PBh>uOXIn8b~t>i5iz%O$RJ?F&7mNQtzo|Few-W;sk+bXDeROa5Mm3`dy?%ic?{%Dzn7UT$$jwqA- z!#vIgP^Yp|QeR07ZVwf#1oGwOh`=HlW>L2&nJByFVTE>(l}`4*}F;@1M@=3(gXswzmkkj zRd)Pi5fbB0cjx$0gv7lRAq0OGAug6?X09%5e>9xGX0oURd57*_WnSVndH#1RB;sWe zyGfdmexvF7DsQP3p8W7~8kE}`mxu^Drwu&%Ym*MYL>9DSvySyv$;=C<1ZTJpMx}OH z7pGOTKf<}l3t7w}@tpl&#_{`(-^ zDK7!U$(EN=_}Kc-}>>RZjeU3MmJ63e)GNxob-&s+!>=@Wu*m+n(rL7#Yu=X z_FMQP<6~+{UIM5LO6$Ro6rt1GF?C2BD5AjY$J7Ynf9xee-Z&VC|9>xW{ZlVRaVY6< zBj!0Ra33F%seFyL++;+AetrT{P0+A@-|Y1}ns=z}>#9G(2hLl%2+668buuzHRJuGr zH4q6n?FQEu*-na$#jfAwH+6iw{?XVDi4cV_x7Ui8mx;Wg4lQ{P!(V-=lE=lV^uv0C zAHMb|QjST|>i$*KOiB)diw81vxeSwL{t{vJ28`3hu*k)Oq{p)y*8v_d-X3%aha~?# zEZW|U?^x;|CyZGdcnRl{j!Hj$3J)VpBkCqD${uX$?3l#QSJuM?2iB$X4Ps(lrmy(r+ z^oWHV!B8#sQ0YRHddjP<0<2w3OH4)yliO3S48b(k^fsQ!U*_iO@XCa2hST|trb5Ob zL7M!u+i>C{E-^&TWP=;rgf1T_0WKINZI4BpFXj4wuCS!?eB1AO=_HnyYVrS(N`DlE zQ3+iN-`Oz$r^)wdDW|R6KB{ke$#%BO5~HL-7px0 zn7Wp?I9s}im@_aR08kc=u3WiaIaLncW16Gaj=!4=yQqC+u9%5o3mNVR4eZIqwN*Z5 zX=)M?ARrv_PrD@fkfDCsrM2(za3`0Sg*a(du*D$$rmn1ooYZTP@ZiDBg>rUyY>2pZ zcG#qo5-$Wha`LO7L8OgnVdI#~?LHwYv7eMIQLGh^n02XTenw9l@q(TE(MD(lcAO|? z!@(&+D`|zAKnc@piQK}9jdYV$Z*^KZz3?YGBE9+yd=sn*k)O9{`BN6`D;o9lvNMO) zvXveAgbgydX5Mqi*fo(@RhmWF3^^jD*_=iU96LZr0u(OSXnF1ia6Y;yQDiB6B#tA=>h+M_MswdiTnpP85{IPFxE=Crvz@_rSzT zCg)|ND9d@&JsnlbS^=WTjC=%rW3F8&=I`nr_Y39w9^%aqao7eD#%>tuQ z=8yZl<7c_~(oH?S(^MEYoHS(ot-uZsc$kJkap0%Y9$^)MPsjb`L^G^5p^1ymt_ly~ z&NGsDn+Li@+m^WBez-E=z-NUqkDnCS3G2m#V|r;V9zEB%F?@rw`0s7Ut(B_vhlaD_Yen@f@KLdA32Z zW3xwtjh6xW~sF3AdX!6nT_5{yy%fXuod5N z3gdrOB`UIqg5YY|4Y~}}s-MRSd4>a?9{F9VxPO-Dl37CdN{)~1)6h{J#LG!k z(0-oiPD<6K@zA$6bJ>;CrF~NR z3pAQygk%5wU7+4Wpzg#YuK%q1!-q~2Zt+_22v9j&7jL4z-+TcD@t3Crn3^pI#wX-| z8c1uEAxlcIUbrR#0fG5Hlyf&WQgU{%HZyT$HMX)p(m{6Ao&6|ubE2(XN$Wzp4WAO& zKx1p7vw5;Ii_Bc-FhrqMMq^8lx4^VdRMjvwT%OFr-ZYKPdikU`7#F@L^NxrMfWJy!cV;I1JVBA$antSF|tF~{&lyHRuPzLiy^aSE@N*5 zxtqf@McYk65HIn}jW@dO18v?;$tNj=c-2kHe)Db9{Y~CQF;hBQ+RO~e5OlttL9MHG5cI9#|<}P3Z5C`hI=JKYuMmV z^uCwirK$BM;_4-E?d5>h*mXWe}r%@BG5P3e(;;{ie#10qNS0Rb8_7y1Vb52OdA2eh6DugJOg`VCYE z>?Pz=Cxjz1^6COwNjU&$debwtAwmc;u?m-k~&EwdQ_>A3GPvMIq8uf_+WZ3w+8 zVQ8TId{rhybjOnlC}t014~h4o68GGFGg%N7NSEIk-lQ;A;xRq>DB?)SZ{AqIflshN zn(4a1b|AB#$S|+z%9kz@Ys^}*EbHW(OZ!+6<0-Hq;K0+4;jD?JERuOFD@UpPBl6@j zz3gHckG+(vXM{rTBR;sY?`*TraSExf-rXt zI}fOwlctj|m5$eSrIe~Jl4VuR4c*Pvm&pls(aeNX^r6#33lfYe1gTTz9BG0FGQ|t^ zHgaBZf~tZmnF@3O@kuqc1Jlg@8(PULQp|yw+v=HpA`%by)(ShfPcClzP8zX~X}U#6 z=CVw{+7;O3r=HRUA;G~*&sL5EshFaY%}F7TiMF$k`$s-KH~rCaW;OOM-l*)Qd0|2g|yaV%NDmNUrbO;{8X`I({4Ju zC%rv9PN=2l6V&yz^L;#7gU5^DiqjdV0s<}Vv12lFBMR!oG-^)Tf-hFG5*U)SgJe8N zgQSkz^-;*zf05ttiPX78MD$ELi@eK0gy7Av@k&2=qp}pKe$19{@hMv$_1#Cl!^df^ zSWhvv!1km{#~|TUgW%4+=1vInHyk7SjmF$E#wlDd!bT(RyE2scjldzSt2533*TGLC z3d$Y#3{^{h+f`esEftHxmGNfb%pO8J4k3>^r$YvrdK+)V;{4s)^lk5WwSVqOGo48n zH18RFDjuQU6R3NRO!uEe+3Rt$$!#3D2aRYaYIYS# zL*r?l1zNu01>dr`xqtBD0 zgYNfxW?*ilZRYPXdUgH>+Nm3?MqsuPYw#1uEhjtwD&WEb(%V%6_8}eibib}63BxlWTlSp>qCOCv*6cBe4qiX-3UiWoUIgDi8`SyNag2=Dr={x-oq~&w3?4@7!HLuwozE_Z86-D>pOVXe)=O>+4cR*2D&{}5&`c7arD7EVUk`r*biJ7Kd0x6#uzB7M!`|{ zARaLGla?#`fjV6*A65MXKBajDy36B@?z=7UnnVuew{J;CSvr8|im%6}fZ{|)DyBLE zLU?S_k03PtsD${NnuE_hC*suZtOwN|%Ax1i?j*ixTMqD&M#2gRE9)>EWFA}d!FuS4 z)6o|bxf}{NE-`Ps(0?ko!q$Xg_utu2kEw#r%ArHuZ;Ku02rXDcB+v1B!b_0Na`lAq zoq?Tf_KpP$k&%JI_CTwMmQF17eCUivuiO><@rN>a-b>rSn6e_Wguy4jAGm#1O#1}E z=5Ol>CG_G}afA?$f{IyZ&`1d}4MIb%Mx(XKB=l^>vpy+fN5hdyDsUBH_kUz55_OZ{ zAkM=s5(>F6Af-&w!2e0e$<5SSay}f@)_c)DSM0X1LB&$hP=`wtRfn16*P%?E$Y+!t zV*mDF82V0hE4sUM?3EwV493)tz-yzVuGs82v0H)zje6HF$xa73b{43DuYyP$FVmYIdKK&Axh zqxr)s*#NIH>R`MwEOMITzuQNhxEAz(V6VdDG)}@l-Ikr(-MVavMy}?~_6HR&Sq^kl z&&ru6>%K}KnlOXznH1Q6gA$dFf`)7HhB-GR^XyyJELs5rCt<9BH&6~RraGt$%YhD9 zkt-%)N#;k-HO;h;YLp&J{bp)u69atfqC$k$riQ|GI$KT4#(>H=!I-1FHzNHps!-cP zs*Ht=0X-of7P>jBxv*GNMukYx1{!Zljrx=_MP7DXI9z!OP+x@vIOZD*@TEbTHlnO+Uk4V=B|e-lC@bJ-<5RJKMN_{*O1$C z@<7w5{oGYA;YkrLf@ya;r;Y?~#Vc=RV4pLP38WOmgQT_lWePj=V-w$ddwOZcmdrDZ zn6F`-f#-#p*6pIBL7C&En}zS01V@I&p zfA>TFIm6LU)(6bA0mtUSlp)j8p$fqOnr4~~w4h~*DnD5x=Z+vb}wcoR7+Fs{OqhbknI=DTth zlL>eQX|}GZXPU$m4*cK9AJS+`l-iixNf2g!X5Fu^Dc~gjxNJZ0sT^0X{`!1l25N*R zUGdARDi!~-nMElusf-j^rfhhd(S-AohAH)QiGn|sogtmadqnaMa8$@6U$=xbf@vRr zYSP#cOvueXF_562kXd#piu*rnixpi*KbOKS1+NCJ^7xvLBfQjF7J#dyqww7 z2#93|x2_XT2<)YdAnTZR4%SHfTHQ>m>9H0=5X+bO7gpA$KLc7?4url4d_Q)6XpJr_ zxA-ymMx%v^Cn<`K+|VO5-F0OW8F}IAz1?9_G;j4(JvL8pq+$ywm?c4maWS$fl{Uw@ zZYS~^j=WQaRH}*CMR36O^GUCZ1$i?Ag)DNm4!NeEp|M$K4J*YEua8HS6~ss@?OYe| zn-48?yRrSnypPA+|#%uvSKio^YC4vM_;qk zzt-SpcHhED>nNpjor-rV{ye_PmY2 zT6uG1>sK3vn8#MJc)%R)icU_G_c7P59)pW!fmTzl1XV9r77SD7U8qj$O<{AA171h( zHu+}h=t1J^PSPbS&t_tShF#E3f2wKo&L_|aw~)(Mu&jr@(43abO=<^EgXR7Bdw&K( z5O%%nIgg@{$0?aj?3S`@8RxPN;}qtWBHe{#Ofo4K&F*+&udjV18c#_xS-O=^UhkOZ zroPPez(t(zT#D+I%6@j;;8MB)w<~KPv-;2>%eQKbXV{cfsGlef*HDe;!*z-8Hf~EI z_h|S(l5jkumUxC88yuU@)D=s>TuUpok6sc=R;;#X#|VKbZw#wTKp+dQuoL;N_G~{4 zso062_f18Z!Jj6?)!?9TxoGz=WrHIC^;^_xfHSU?>g1HN!`EBd*sxs|~ zvK=LEQHoQ1zPUd$BHCp(3uxMJ3VQQ^h=}-&I1>AHrv_CZXc@D1#)C${Vx4qHG_qn zb;KfQIVMNkNy@H_H?x8!Y;@VdGY%c+V3xi@O4BknYH$^`a|V4}PKQ*3C;3jQB~Cd+ z1Php4)IF=ZcC#)+UFGuH_~I*_X|&B0PR1cM_ju0ox&86y-JxCk=aO3RRTuK%da>an z{Ge~~qY>FLr{HrR(}GpViuRmp-3jrgadXY!@bcA`+-UbqE|7A+hFuWQpDA|S03TFo z&w^o#^x9J_KIO%wd1`Mt&FwGlqRKu+g7U6aKE6|wL?(T>(ItxsnAdwYlO~jH{ngrg zxlG}^IydlLx0-7VyIl;sqCUa1ub4wsAALquK%Qm^Sq=hSzv{hf1X5v8!#hQVO>#PR zF-1dlPMmo`ZSTsG(E=+X*IxFwW0T){wzmlKTd(|t|Y1ko1F3rGw__M`fzW$0eNXPEH!s9il?NAR_!G^28{7`SP zgW|cX_Xh))a|2;>w%Cc;^%)cKG5BSr^dIB_M3_b;@fUf(>}4?(^Tk?b>R|G+q~++s zX5!#%_G^*&#fJG`N7>7*i%KwX=%&EwTT4CS=WhLqbNC$JNN0a;#VWg7Yvus+MTR*E z;>-Q<`*Qc!Cq}W~U2dJmQ147j%6<@6BsQ>ONUPuk?l#1EspsDGF}J^uzmP-zFiV6~ z4T%_*A~_@ zsrajApheI}fR3T97u8!T*wc7(n{qS?J^&JqWU$1Nh_xk1zcCPho0YmbV58x3#lfjP44(!tHe!wuiYV`5s*v5Y~GWdDK>D23sGjrhI<17Jvz1Ae5N_p z_^sGR+f(4C&P(s#aje1T!{h6p*Juq8i#smd?kiPVC+2R;zWp%}*s^CvzxGLYp3XK` z!1Ecu<>s8*2THIlZTkXP<;Si1^Y;7IpYk^(6w8a8_RlY&{JX>c-TuQXDN3?`2l%`F z{ojGV+JTp>_=_3-_rSk9g8v$L{NnxlfBS=f$NAmu`6rSV;(vHQe-HlMYWQa`;fsRu z@+W>b9{vvSyUpWIfYld;8PXzG45dN>u`FrT^3e}&X&P0Fw_}}%a-^2f2 z3j7%i0U^Z#0r4M&!SCUJAJ6|9-p%%x@PCc&O0uvoMExb+qC&{Na6Opw*SG%zw11q3 literal 0 HcmV?d00001 -- 2.39.5