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.

TestPackage.java 46KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  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.openxml4j.opc;
  16. import static org.apache.poi.openxml4j.OpenXML4JTestDataSamples.getOutputFile;
  17. import static org.apache.poi.openxml4j.OpenXML4JTestDataSamples.getSampleFile;
  18. import static org.apache.poi.openxml4j.OpenXML4JTestDataSamples.getSampleFileName;
  19. import static org.apache.poi.openxml4j.OpenXML4JTestDataSamples.openSampleStream;
  20. import static org.apache.poi.openxml4j.opc.PackagingURIHelper.createPartName;
  21. import static org.apache.poi.xssf.usermodel.XSSFRelation.NS_WORDPROCESSINGML;
  22. import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
  23. import static org.junit.jupiter.api.Assertions.assertEquals;
  24. import static org.junit.jupiter.api.Assertions.assertFalse;
  25. import static org.junit.jupiter.api.Assertions.assertNotNull;
  26. import static org.junit.jupiter.api.Assertions.assertNull;
  27. import static org.junit.jupiter.api.Assertions.assertThrows;
  28. import static org.junit.jupiter.api.Assertions.assertTrue;
  29. import static org.junit.jupiter.api.Assertions.fail;
  30. import java.io.BufferedInputStream;
  31. import java.io.File;
  32. import java.io.FileInputStream;
  33. import java.io.FileOutputStream;
  34. import java.io.IOException;
  35. import java.io.InputStream;
  36. import java.io.OutputStream;
  37. import java.io.PushbackInputStream;
  38. import java.net.URI;
  39. import java.net.URISyntaxException;
  40. import java.nio.charset.StandardCharsets;
  41. import java.util.Arrays;
  42. import java.util.Enumeration;
  43. import java.util.HashMap;
  44. import java.util.List;
  45. import java.util.TreeMap;
  46. import java.util.function.BiConsumer;
  47. import java.util.regex.Pattern;
  48. import java.util.stream.Stream;
  49. import com.google.common.hash.Hashing;
  50. import com.google.common.io.Files;
  51. import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
  52. import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
  53. import org.apache.commons.compress.archivers.zip.ZipFile;
  54. import org.apache.commons.io.output.CountingOutputStream;
  55. import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
  56. import org.apache.logging.log4j.LogManager;
  57. import org.apache.logging.log4j.Logger;
  58. import org.apache.poi.EncryptedDocumentException;
  59. import org.apache.poi.POIDataSamples;
  60. import org.apache.poi.POITestCase;
  61. import org.apache.poi.extractor.ExtractorFactory;
  62. import org.apache.poi.extractor.POITextExtractor;
  63. import org.apache.poi.ooxml.POIXMLException;
  64. import org.apache.poi.ooxml.POIXMLTypeLoader;
  65. import org.apache.poi.ooxml.util.DocumentHelper;
  66. import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
  67. import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
  68. import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
  69. import org.apache.poi.openxml4j.opc.internal.ContentTypeManager;
  70. import org.apache.poi.openxml4j.opc.internal.FileHelper;
  71. import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart;
  72. import org.apache.poi.openxml4j.opc.internal.ZipHelper;
  73. import org.apache.poi.openxml4j.util.ZipSecureFile;
  74. import org.apache.poi.sl.usermodel.SlideShow;
  75. import org.apache.poi.sl.usermodel.SlideShowFactory;
  76. import org.apache.poi.ss.usermodel.Workbook;
  77. import org.apache.poi.ss.usermodel.WorkbookFactory;
  78. import org.apache.poi.util.IOUtils;
  79. import org.apache.poi.util.TempFile;
  80. import org.apache.poi.xssf.XSSFTestDataSamples;
  81. import org.apache.poi.xssf.streaming.SXSSFWorkbook;
  82. import org.apache.poi.xssf.usermodel.XSSFRelation;
  83. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  84. import org.apache.poi.xwpf.usermodel.XWPFRelation;
  85. import org.junit.jupiter.api.Disabled;
  86. import org.junit.jupiter.api.Test;
  87. import org.junit.jupiter.api.function.Executable;
  88. import org.junit.jupiter.params.ParameterizedTest;
  89. import org.junit.jupiter.params.provider.CsvSource;
  90. import org.w3c.dom.Document;
  91. import org.w3c.dom.Element;
  92. import org.w3c.dom.NodeList;
  93. import org.xml.sax.SAXException;
  94. import org.xml.sax.SAXParseException;
  95. public final class TestPackage {
  96. private static final Logger LOG = LogManager.getLogger(TestPackage.class);
  97. private static final String CONTENT_EXT_PROPS = "application/vnd.openxmlformats-officedocument.extended-properties+xml";
  98. private static final POIDataSamples xlsSamples = POIDataSamples.getSpreadSheetInstance();
  99. @Test
  100. void isStrictOoxmlFormat() throws IOException, InvalidFormatException {
  101. try (OPCPackage p = OPCPackage.open(getSampleFileName("TestPackageCommon.docx"), PackageAccess.READ)) {
  102. assertFalse(p.isStrictOoxmlFormat());
  103. }
  104. try (OPCPackage p = OPCPackage.open(xlsSamples.getFile("sample.xlsx"), PackageAccess.READ)) {
  105. assertFalse(p.isStrictOoxmlFormat());
  106. }
  107. try (OPCPackage p = OPCPackage.open(xlsSamples.getFile("sample.strict.xlsx"), PackageAccess.READ)) {
  108. assertTrue(p.isStrictOoxmlFormat());
  109. }
  110. }
  111. /**
  112. * Test that just opening and closing the file doesn't alter the document.
  113. */
  114. @Test
  115. void openSave() throws IOException, InvalidFormatException {
  116. String originalFile = getSampleFileName("TestPackageCommon.docx");
  117. File targetFile = getOutputFile("TestPackageOpenSaveTMP.docx");
  118. try (OPCPackage p = OPCPackage.open(originalFile, PackageAccess.READ_WRITE)) {
  119. try {
  120. p.save(targetFile.getAbsoluteFile());
  121. // Compare the original and newly saved document
  122. assertTrue(targetFile.exists());
  123. ZipFileAssert.assertEquals(new File(originalFile), targetFile);
  124. assertTrue(targetFile.delete());
  125. } finally {
  126. // use revert to not re-write the input file
  127. p.revert();
  128. }
  129. }
  130. }
  131. /**
  132. * Test that when we create a new Package, we give it
  133. * the correct default content types
  134. */
  135. @Test
  136. void createGetsContentTypes()
  137. throws IOException, InvalidFormatException, SecurityException, IllegalArgumentException {
  138. File targetFile = getOutputFile("TestCreatePackageTMP.docx");
  139. // Zap the target file, in case of an earlier run
  140. if(targetFile.exists()) {
  141. assertTrue(targetFile.delete());
  142. }
  143. try (OPCPackage pkg = OPCPackage.create(targetFile)) {
  144. try {
  145. // Check it has content types for rels and xml
  146. ContentTypeManager ctm = getContentTypeManager(pkg);
  147. assertEquals("application/xml", ctm.getContentType(createPartName("/foo.xml")));
  148. assertEquals(ContentTypes.RELATIONSHIPS_PART, ctm.getContentType(createPartName("/foo.rels")));
  149. assertNull(ctm.getContentType(createPartName("/foo.txt")));
  150. } finally {
  151. pkg.revert();
  152. }
  153. }
  154. }
  155. /**
  156. * Test package creation.
  157. */
  158. @Test
  159. void createPackageAddPart() throws IOException, InvalidFormatException {
  160. File targetFile = getOutputFile("TestCreatePackageTMP.docx");
  161. File expectedFile = getSampleFile("TestCreatePackageOUTPUT.docx");
  162. // Zap the target file, in case of an earlier run
  163. if(targetFile.exists()) {
  164. assertTrue(targetFile.delete());
  165. }
  166. // Create a package
  167. OPCPackage pkg = OPCPackage.create(targetFile);
  168. PackagePartName corePartName = createPartName("/word/document.xml");
  169. pkg.addRelationship(corePartName, TargetMode.INTERNAL,
  170. PackageRelationshipTypes.CORE_DOCUMENT, "rId1");
  171. PackagePart corePart = pkg.createPart(corePartName, XWPFRelation.DOCUMENT.getContentType());
  172. Document doc = DocumentHelper.createDocument();
  173. Element elDocument = doc.createElementNS(NS_WORDPROCESSINGML, "w:document");
  174. doc.appendChild(elDocument);
  175. Element elBody = doc.createElementNS(NS_WORDPROCESSINGML, "w:body");
  176. elDocument.appendChild(elBody);
  177. Element elParagraph = doc.createElementNS(NS_WORDPROCESSINGML, "w:p");
  178. elBody.appendChild(elParagraph);
  179. Element elRun = doc.createElementNS(NS_WORDPROCESSINGML, "w:r");
  180. elParagraph.appendChild(elRun);
  181. Element elText = doc.createElementNS(NS_WORDPROCESSINGML, "w:t");
  182. elRun.appendChild(elText);
  183. elText.setTextContent("Hello Open XML !");
  184. StreamHelper.saveXmlInStream(doc, corePart.getOutputStream());
  185. pkg.close();
  186. ZipFileAssert.assertEquals(expectedFile, targetFile);
  187. assertTrue(targetFile.delete());
  188. }
  189. /**
  190. * Tests that we can create a new package, add a core
  191. * document and another part, save and re-load and
  192. * have everything setup as expected
  193. */
  194. @Test
  195. void createPackageWithCoreDocument() throws IOException, InvalidFormatException, URISyntaxException, SAXException {
  196. UnsynchronizedByteArrayOutputStream baos = UnsynchronizedByteArrayOutputStream.builder().get();
  197. try (OPCPackage pkg = OPCPackage.create(baos)) {
  198. // Add a core document
  199. PackagePartName corePartName = createPartName("/xl/workbook.xml");
  200. // Create main part relationship
  201. pkg.addRelationship(corePartName, TargetMode.INTERNAL, PackageRelationshipTypes.CORE_DOCUMENT, "rId1");
  202. // Create main document part
  203. PackagePart corePart = pkg.createPart(corePartName, XSSFRelation.WORKBOOK.getContentType());
  204. // Put in some dummy content
  205. try (OutputStream coreOut = corePart.getOutputStream()) {
  206. coreOut.write("<dummy-xml />".getBytes(StandardCharsets.UTF_8));
  207. }
  208. // And another bit
  209. PackagePartName sheetPartName = createPartName("/xl/worksheets/sheet1.xml");
  210. PackageRelationship rel = corePart.addRelationship(
  211. sheetPartName, TargetMode.INTERNAL, XSSFRelation.WORKSHEET.getRelation(), "rSheet1");
  212. assertNotNull(rel);
  213. PackagePart part = pkg.createPart(sheetPartName, XSSFRelation.WORKSHEET.getContentType());
  214. assertNotNull(part);
  215. // Dummy content again
  216. try (OutputStream coreOut = corePart.getOutputStream()) {
  217. coreOut.write("<dummy-xml2 />".getBytes(StandardCharsets.UTF_8));
  218. }
  219. //add a relationship with internal target: "#Sheet1!A1"
  220. corePart.addRelationship(new URI("#Sheet1!A1"), TargetMode.INTERNAL, PackageRelationshipTypes.HYPERLINK_PART, "rId2");
  221. // Check things are as expected
  222. PackageRelationshipCollection coreRels =
  223. pkg.getRelationshipsByType(PackageRelationshipTypes.CORE_DOCUMENT);
  224. assertEquals(1, coreRels.size());
  225. PackageRelationship coreRel = coreRels.getRelationship(0);
  226. assertNotNull(coreRel);
  227. assertEquals("/", coreRel.getSourceURI().toString());
  228. assertEquals("/xl/workbook.xml", coreRel.getTargetURI().toString());
  229. assertNotNull(pkg.getPart(coreRel));
  230. }
  231. // Save and re-load
  232. File tmp = TempFile.createTempFile("testCreatePackageWithCoreDocument", ".zip");
  233. try (OutputStream fout = new FileOutputStream(tmp)) {
  234. baos.writeTo(fout);
  235. fout.flush();
  236. }
  237. try (OPCPackage pkg = OPCPackage.open(tmp.getPath())) {
  238. // Check still right
  239. PackageRelationshipCollection coreRels = pkg.getRelationshipsByType(PackageRelationshipTypes.CORE_DOCUMENT);
  240. assertEquals(1, coreRels.size());
  241. PackageRelationship coreRel = coreRels.getRelationship(0);
  242. assertNotNull(coreRel);
  243. assertEquals("/", coreRel.getSourceURI().toString());
  244. assertEquals("/xl/workbook.xml", coreRel.getTargetURI().toString());
  245. PackagePart corePart = pkg.getPart(coreRel);
  246. assertNotNull(corePart);
  247. PackageRelationshipCollection rels = corePart.getRelationshipsByType(PackageRelationshipTypes.HYPERLINK_PART);
  248. assertEquals(1, rels.size());
  249. PackageRelationship rel = rels.getRelationship(0);
  250. assertNotNull(rel);
  251. assertEquals("#Sheet1!A1", rel.getTargetURI().toString());
  252. assertMSCompatibility(pkg);
  253. }
  254. }
  255. private void assertMSCompatibility(OPCPackage pkg) throws IOException, InvalidFormatException, SAXException {
  256. PackagePartName relName = createPartName(PackageRelationship.getContainerPartRelationship());
  257. PackagePart relPart = pkg.getPart(relName);
  258. Document xmlRelationshipsDoc = DocumentHelper.readDocument(relPart.getInputStream());
  259. Element root = xmlRelationshipsDoc.getDocumentElement();
  260. NodeList nodeList = root.getElementsByTagName(PackageRelationship.RELATIONSHIP_TAG_NAME);
  261. int nodeCount = nodeList.getLength();
  262. for (int i = 0; i < nodeCount; i++) {
  263. Element element = (Element) nodeList.item(i);
  264. String value = element.getAttribute(PackageRelationship.TARGET_ATTRIBUTE_NAME);
  265. assertTrue(value.charAt(0) != '/', "Root target must not start with a leading slash ('/'): " + value);
  266. }
  267. }
  268. /**
  269. * Test package opening.
  270. */
  271. @Test
  272. void openPackage() throws IOException, InvalidFormatException {
  273. File targetFile = getOutputFile("TestOpenPackageTMP.docx");
  274. File inputFile = getSampleFile("TestOpenPackageINPUT.docx");
  275. File expectedFile = getSampleFile("TestOpenPackageOUTPUT.docx");
  276. // Copy the input file in the output directory
  277. FileHelper.copyFile(inputFile, targetFile);
  278. // Create a package
  279. OPCPackage pkg = OPCPackage.open(targetFile.getAbsolutePath());
  280. // Modify core part
  281. PackagePartName corePartName = createPartName("/word/document.xml");
  282. PackagePart corePart = pkg.getPart(corePartName);
  283. // Delete some part to have a valid document
  284. for (PackageRelationship rel : corePart.getRelationships()) {
  285. corePart.removeRelationship(rel.getId());
  286. pkg.removePart(createPartName(PackagingURIHelper
  287. .resolvePartUri(corePart.getPartName().getURI(), rel
  288. .getTargetURI())));
  289. }
  290. // Create a content
  291. Document doc = DocumentHelper.createDocument();
  292. Element elDocument = doc.createElementNS(NS_WORDPROCESSINGML, "w:document");
  293. doc.appendChild(elDocument);
  294. Element elBody = doc.createElementNS(NS_WORDPROCESSINGML, "w:body");
  295. elDocument.appendChild(elBody);
  296. Element elParagraph = doc.createElementNS(NS_WORDPROCESSINGML, "w:p");
  297. elBody.appendChild(elParagraph);
  298. Element elRun = doc.createElementNS(NS_WORDPROCESSINGML, "w:r");
  299. elParagraph.appendChild(elRun);
  300. Element elText = doc.createElementNS(NS_WORDPROCESSINGML, "w:t");
  301. elRun.appendChild(elText);
  302. elText.setTextContent("Hello Open XML !");
  303. StreamHelper.saveXmlInStream(doc, corePart.getOutputStream());
  304. // Save and close
  305. assertDoesNotThrow(pkg::close);
  306. ZipFileAssert.assertEquals(expectedFile, targetFile);
  307. assertTrue(targetFile.delete());
  308. }
  309. /**
  310. * Checks that we can write a package to a simple
  311. * OutputStream, in addition to the normal writing
  312. * to a file
  313. */
  314. @Test
  315. void saveToOutputStream() throws IOException, InvalidFormatException {
  316. String originalFile = getSampleFileName("TestPackageCommon.docx");
  317. File targetFile = getOutputFile("TestPackageOpenSaveTMP.docx");
  318. try (OPCPackage p = OPCPackage.open(originalFile, PackageAccess.READ_WRITE)) {
  319. try {
  320. try (FileOutputStream fout = new FileOutputStream(targetFile)) {
  321. p.save(fout);
  322. }
  323. // Compare the original and newly saved document
  324. assertTrue(targetFile.exists());
  325. ZipFileAssert.assertEquals(new File(originalFile), targetFile);
  326. assertTrue(targetFile.delete());
  327. } finally {
  328. // use revert to not re-write the input file
  329. p.revert();
  330. }
  331. }
  332. }
  333. /**
  334. * Checks that we can open+read a package from a
  335. * simple InputStream, in addition to the normal
  336. * reading from a file
  337. */
  338. @Test
  339. void openFromInputStream() throws IOException, InvalidFormatException {
  340. String originalFile = getSampleFileName("TestPackageCommon.docx");
  341. try (FileInputStream finp = new FileInputStream(originalFile);
  342. OPCPackage p = OPCPackage.open(finp)) {
  343. try {
  344. assertNotNull(p);
  345. assertNotNull(p.getRelationships());
  346. assertEquals(12, p.getParts().size());
  347. // Check it has the usual bits
  348. assertTrue(p.hasRelationships());
  349. assertTrue(p.containPart(createPartName("/_rels/.rels")));
  350. } finally {
  351. p.revert();
  352. }
  353. }
  354. }
  355. /**
  356. * TODO: fix and enable
  357. */
  358. @Test
  359. @Disabled
  360. void removePartRecursive() throws IOException, InvalidFormatException, URISyntaxException {
  361. String originalFile = getSampleFileName("TestPackageCommon.docx");
  362. File targetFile = getOutputFile("TestPackageRemovePartRecursiveOUTPUT.docx");
  363. File tempFile = getOutputFile("TestPackageRemovePartRecursiveTMP.docx");
  364. try (OPCPackage p = OPCPackage.open(originalFile, PackageAccess.READ_WRITE)) {
  365. p.removePartRecursive(createPartName(new URI("/word/document.xml")));
  366. p.save(tempFile.getAbsoluteFile());
  367. // Compare the original and newly saved document
  368. assertTrue(targetFile.exists());
  369. ZipFileAssert.assertEquals(targetFile, tempFile);
  370. assertTrue(targetFile.delete());
  371. p.revert();
  372. }
  373. }
  374. @Test
  375. void deletePart() throws InvalidFormatException, IOException {
  376. final TreeMap<PackagePartName, String> expectedValues = new TreeMap<>();
  377. final TreeMap<PackagePartName, String> values = new TreeMap<>();
  378. // Expected values
  379. expectedValues.put(createPartName("/_rels/.rels"), ContentTypes.RELATIONSHIPS_PART);
  380. expectedValues.put(createPartName("/docProps/app.xml"), CONTENT_EXT_PROPS);
  381. expectedValues.put(createPartName("/docProps/core.xml"), ContentTypes.CORE_PROPERTIES_PART);
  382. expectedValues.put(createPartName("/word/fontTable.xml"), XWPFRelation.FONT_TABLE.getContentType());
  383. expectedValues.put(createPartName("/word/media/image1.gif"), XWPFRelation.IMAGE_GIF.getContentType());
  384. expectedValues.put(createPartName("/word/settings.xml"), XWPFRelation.SETTINGS.getContentType());
  385. expectedValues.put(createPartName("/word/styles.xml"), XWPFRelation.STYLES.getContentType());
  386. expectedValues.put(createPartName("/word/theme/theme1.xml"), XWPFRelation.THEME.getContentType());
  387. expectedValues.put(createPartName("/word/webSettings.xml"), XWPFRelation.WEB_SETTINGS.getContentType());
  388. String filepath = getSampleFileName("sample.docx");
  389. try (OPCPackage p = OPCPackage.open(filepath, PackageAccess.READ_WRITE)) {
  390. try {
  391. // Remove the core part
  392. p.deletePart(createPartName("/word/document.xml"));
  393. for (PackagePart part : p.getParts()) {
  394. values.put(part.getPartName(), part.getContentType());
  395. LOG.atDebug().log(part.getPartName());
  396. }
  397. // Compare expected values with values return by the package
  398. for (PackagePartName partName : expectedValues.keySet()) {
  399. assertNotNull(values.get(partName));
  400. assertEquals(expectedValues.get(partName), values.get(partName));
  401. }
  402. } finally {
  403. // Don't save modifications
  404. p.revert();
  405. }
  406. }
  407. }
  408. @Test
  409. void deletePartRecursive() throws InvalidFormatException, IOException {
  410. final TreeMap<PackagePartName, String> expectedValues = new TreeMap<>();
  411. final TreeMap<PackagePartName, String> values = new TreeMap<>();
  412. // Expected values
  413. expectedValues.put(createPartName("/_rels/.rels"), ContentTypes.RELATIONSHIPS_PART);
  414. expectedValues.put(createPartName("/docProps/app.xml"), CONTENT_EXT_PROPS);
  415. expectedValues.put(createPartName("/docProps/core.xml"), ContentTypes.CORE_PROPERTIES_PART);
  416. String filepath = getSampleFileName("sample.docx");
  417. try (OPCPackage p = OPCPackage.open(filepath, PackageAccess.READ_WRITE)) {
  418. try {
  419. // Remove the core part
  420. p.deletePartRecursive(createPartName("/word/document.xml"));
  421. for (PackagePart part : p.getParts()) {
  422. values.put(part.getPartName(), part.getContentType());
  423. LOG.atDebug().log(part.getPartName());
  424. }
  425. // Compare expected values with values return by the package
  426. for (PackagePartName partName : expectedValues.keySet()) {
  427. assertNotNull(values.get(partName));
  428. assertEquals(expectedValues.get(partName), values.get(partName));
  429. }
  430. } finally {
  431. // Don't save modifications
  432. p.revert();
  433. }
  434. }
  435. }
  436. /**
  437. * Test that we can open a file by path, and then
  438. * write changes to it.
  439. */
  440. @Test
  441. void openFileThenOverwrite() throws IOException, InvalidFormatException {
  442. File tempFile = TempFile.createTempFile("poiTesting","tmp");
  443. File origFile = getSampleFile("TestPackageCommon.docx");
  444. FileHelper.copyFile(origFile, tempFile);
  445. // Open and close the temp file
  446. try (OPCPackage p = OPCPackage.open(tempFile.toString(), PackageAccess.READ_WRITE)) {
  447. assertNotNull(p);
  448. }
  449. // Delete it
  450. assertTrue(tempFile.delete());
  451. // Reset
  452. FileHelper.copyFile(origFile, tempFile);
  453. try (OPCPackage p = OPCPackage.open(tempFile.toString(), PackageAccess.READ_WRITE)) {
  454. // Save it to the same file - not allowed
  455. assertThrows(InvalidOperationException.class, () -> p.save(tempFile),
  456. "You shouldn't be able to call save(File) to overwrite the current file");
  457. }
  458. // Delete it
  459. assertTrue(tempFile.delete());
  460. // Open it read only, then close and delete - allowed
  461. FileHelper.copyFile(origFile, tempFile);
  462. try (OPCPackage p = OPCPackage.open(tempFile.toString(), PackageAccess.READ)) {
  463. assertNotNull(p);
  464. }
  465. assertTrue(tempFile.delete());
  466. }
  467. /**
  468. * Test that we can open a file by path, save it
  469. * to another file, then delete both
  470. */
  471. @Test
  472. void openFileThenSaveDelete() throws IOException, InvalidFormatException {
  473. File tempFile = TempFile.createTempFile("poiTesting","tmp");
  474. File tempFile2 = TempFile.createTempFile("poiTesting","tmp");
  475. File origFile = getSampleFile("TestPackageCommon.docx");
  476. FileHelper.copyFile(origFile, tempFile);
  477. // Open the temp file
  478. try (OPCPackage p = OPCPackage.open(tempFile.toString(), PackageAccess.READ_WRITE)) {
  479. // Save it to a different file
  480. p.save(tempFile2);
  481. }
  482. // Delete both the files
  483. assertTrue(tempFile.delete());
  484. assertTrue(tempFile2.delete());
  485. }
  486. private static ContentTypeManager getContentTypeManager(OPCPackage pkg) {
  487. return POITestCase.getFieldValue(OPCPackage.class, pkg, ContentTypeManager.class, "contentTypeManager");
  488. }
  489. @Test
  490. void getPartsByName() throws InvalidFormatException, IOException {
  491. String filepath = getSampleFileName("sample.docx");
  492. try (OPCPackage pkg = OPCPackage.open(filepath, PackageAccess.READ_WRITE)) {
  493. try {
  494. List<PackagePart> rs = pkg.getPartsByName(Pattern.compile("/word/.*?\\.xml"));
  495. HashMap<String, PackagePart> selected = new HashMap<>();
  496. for (PackagePart p : rs)
  497. selected.put(p.getPartName().getName(), p);
  498. assertEquals(6, selected.size());
  499. assertTrue(selected.containsKey("/word/document.xml"));
  500. assertTrue(selected.containsKey("/word/fontTable.xml"));
  501. assertTrue(selected.containsKey("/word/settings.xml"));
  502. assertTrue(selected.containsKey("/word/styles.xml"));
  503. assertTrue(selected.containsKey("/word/theme/theme1.xml"));
  504. assertTrue(selected.containsKey("/word/webSettings.xml"));
  505. } finally {
  506. // use revert to not re-write the input file
  507. pkg.revert();
  508. }
  509. }
  510. }
  511. @Test
  512. void getPartSize() throws IOException, InvalidFormatException {
  513. String filepath = getSampleFileName("sample.docx");
  514. try (OPCPackage pkg = OPCPackage.open(filepath, PackageAccess.READ)) {
  515. int checked = 0;
  516. for (PackagePart part : pkg.getParts()) {
  517. // Can get the size of zip parts
  518. if (part.getPartName().getName().equals("/word/document.xml")) {
  519. checked++;
  520. assertEquals(ZipPackagePart.class, part.getClass());
  521. assertEquals(6031L, part.getSize());
  522. }
  523. if (part.getPartName().getName().equals("/word/fontTable.xml")) {
  524. checked++;
  525. assertEquals(ZipPackagePart.class, part.getClass());
  526. assertEquals(1312L, part.getSize());
  527. }
  528. // But not from the others
  529. if (part.getPartName().getName().equals("/docProps/core.xml")) {
  530. checked++;
  531. assertEquals(PackagePropertiesPart.class, part.getClass());
  532. assertEquals(-1, part.getSize());
  533. }
  534. }
  535. // Ensure we actually found the parts we want to check
  536. assertEquals(3, checked);
  537. }
  538. }
  539. @Test
  540. void replaceContentType() throws IOException, InvalidFormatException {
  541. try (InputStream is = openSampleStream("sample.xlsx");
  542. OPCPackage p = OPCPackage.open(is)) {
  543. try {
  544. ContentTypeManager mgr = getContentTypeManager(p);
  545. assertTrue(mgr.isContentTypeRegister(XSSFRelation.WORKBOOK.getContentType()));
  546. assertFalse(mgr.isContentTypeRegister(XSSFRelation.MACROS_WORKBOOK.getContentType()));
  547. assertTrue(p.replaceContentType(XSSFRelation.WORKBOOK.getContentType(), XSSFRelation.MACROS_WORKBOOK.getContentType()));
  548. assertFalse(mgr.isContentTypeRegister(XSSFRelation.WORKBOOK.getContentType()));
  549. assertTrue(mgr.isContentTypeRegister(XSSFRelation.MACROS_WORKBOOK.getContentType()));
  550. } finally {
  551. p.revert();
  552. }
  553. }
  554. }
  555. @SuppressWarnings("unchecked")
  556. @ParameterizedTest
  557. @CsvSource({
  558. "SampleSS.xls, org.apache.poi.openxml4j.exceptions.OLE2NotOfficeXmlFileException, The supplied data appears to be in the OLE2 Format, You are calling the part of POI that deals with OOXML",
  559. "SampleSS.xml, org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException, The supplied data appears to be a raw XML file, Formats such as Office 2003 XML",
  560. "SampleSS.ods, org.apache.poi.openxml4j.exceptions.ODFNotOfficeXmlFileException, The supplied data appears to be in ODF, Formats like these (eg ODS",
  561. "SampleSS.txt, org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException, No valid entries or contents found, not a valid OOXML"
  562. })
  563. void NonOOXML_File(String file, String exClazzStr, String msg1, String msg2) throws Exception {
  564. Class<? extends Exception> exClazz = (Class<? extends Exception>)Class.forName(exClazzStr);
  565. try (InputStream stream = xlsSamples.openResourceAsStream(file)) {
  566. Executable[] trs = {
  567. () -> OPCPackage.open(stream),
  568. () -> OPCPackage.open(xlsSamples.getFile(file))
  569. };
  570. for (Executable tr : trs) {
  571. Exception ex = assertThrows(exClazz, tr, "Shouldn't be able to open "+file);
  572. Stream.of(msg1, msg2).forEach(mp -> assertTrue(ex.getMessage().contains(mp)));
  573. }
  574. }
  575. }
  576. /**
  577. * Zip bomb handling test
  578. *
  579. * see bug #50090 / #56865
  580. */
  581. @Test
  582. void zipBombCreateAndHandle()
  583. throws IOException, EncryptedDocumentException {
  584. UnsynchronizedByteArrayOutputStream bos = UnsynchronizedByteArrayOutputStream.builder().setBufferSize(2500000).get();
  585. try (ZipFile zipFile = ZipHelper.openZipFile(getSampleFile("sample.xlsx"));
  586. ZipArchiveOutputStream append = new ZipArchiveOutputStream(bos)) {
  587. assertNotNull(zipFile);
  588. // first, copy contents from existing war
  589. Enumeration<? extends ZipArchiveEntry> entries = zipFile.getEntries();
  590. while (entries.hasMoreElements()) {
  591. final ZipArchiveEntry eIn = entries.nextElement();
  592. final ZipArchiveEntry eOut = new ZipArchiveEntry(eIn.getName());
  593. eOut.setTime(eIn.getTime());
  594. eOut.setComment(eIn.getComment());
  595. eOut.setSize(eIn.getSize());
  596. append.putArchiveEntry(eOut);
  597. if (!eOut.isDirectory()) {
  598. try (InputStream is = zipFile.getInputStream(eIn)) {
  599. if ("[Content_Types].xml".equals(eOut.getName())) {
  600. byte[] suffix = "</Types>".getBytes(StandardCharsets.UTF_8);
  601. CountingOutputStream cos = new CountingOutputStream(append);
  602. IOUtils.copy(is, cos, eOut.getSize() - suffix.length);
  603. byte[] spam = new byte[0x7FFF];
  604. Arrays.fill(spam, (byte) ' ');
  605. // 0x7FFF0000 is the maximum for 32-bit zips, but less still works
  606. while (cos.getByteCount() < 0x7FFF00) {
  607. cos.write(spam);
  608. }
  609. cos.write(suffix);
  610. eOut.setSize(cos.getByteCount());
  611. } else {
  612. IOUtils.copy(is, append);
  613. }
  614. }
  615. }
  616. append.closeArchiveEntry();
  617. }
  618. }
  619. IOException ex = assertThrows(IOException.class, () -> WorkbookFactory.create(bos.toInputStream()));
  620. assertTrue(ex.getMessage().contains("Zip bomb detected!"));
  621. }
  622. @Test
  623. void testZipEntityExpansionTerminates() {
  624. IllegalStateException ex = assertThrows(
  625. IllegalStateException.class,
  626. () -> openXmlBombFile("poc-shared-strings.xlsx")
  627. );
  628. assertTrue(ex.getMessage().contains("The text would exceed the max allowed overall size of extracted text."));
  629. }
  630. @Test
  631. void testZipEntityExpansionSharedStringTableEvents() {
  632. boolean before = ExtractorFactory.getThreadPrefersEventExtractors();
  633. ExtractorFactory.setThreadPrefersEventExtractors(true);
  634. try {
  635. IllegalStateException ex = assertThrows(
  636. IllegalStateException.class,
  637. () -> openXmlBombFile("poc-shared-strings.xlsx")
  638. );
  639. assertTrue(ex.getMessage().contains("The text would exceed the max allowed overall size of extracted text."));
  640. } finally {
  641. ExtractorFactory.setThreadPrefersEventExtractors(before);
  642. }
  643. }
  644. @Test
  645. void testZipEntityExpansionExceedsMemory() {
  646. boolean originalFlag = POIXMLTypeLoader.DEFAULT_XML_OPTIONS.disallowDocTypeDeclaration();
  647. try {
  648. POIXMLTypeLoader.DEFAULT_XML_OPTIONS.setDisallowDocTypeDeclaration(false);
  649. IOException ex = assertThrows(
  650. IOException.class,
  651. () -> openXmlBombFile("poc-xmlbomb.xlsx")
  652. );
  653. assertTrue(ex.getMessage().contains("unable to parse shared strings table"));
  654. assertTrue(matchSAXEx(ex));
  655. } finally {
  656. POIXMLTypeLoader.DEFAULT_XML_OPTIONS.setDisallowDocTypeDeclaration(originalFlag);
  657. }
  658. }
  659. @Test
  660. void testZipEntityExpansionExceedsMemory2() {
  661. boolean originalFlag = POIXMLTypeLoader.DEFAULT_XML_OPTIONS.disallowDocTypeDeclaration();
  662. try {
  663. POIXMLTypeLoader.DEFAULT_XML_OPTIONS.setDisallowDocTypeDeclaration(false);
  664. IOException ex = assertThrows(
  665. IOException.class,
  666. () -> openXmlBombFile("poc-xmlbomb-empty.xlsx")
  667. );
  668. assertTrue(ex.getMessage().contains("unable to parse shared strings table"));
  669. assertTrue(matchSAXEx(ex));
  670. } finally {
  671. POIXMLTypeLoader.DEFAULT_XML_OPTIONS.setDisallowDocTypeDeclaration(originalFlag);
  672. }
  673. }
  674. private static boolean matchSAXEx(Exception root) {
  675. for (Throwable t = root; t != null; t = t.getCause()) {
  676. if (t.getClass().isAssignableFrom(SAXParseException.class) &&
  677. t.getMessage().contains("The parser has encountered more than")) {
  678. return true;
  679. }
  680. }
  681. return false;
  682. }
  683. private void openXmlBombFile(String file) throws IOException {
  684. final double minInf = ZipSecureFile.getMinInflateRatio();
  685. ZipSecureFile.setMinInflateRatio(0.002);
  686. try (POITextExtractor extractor = ExtractorFactory.createExtractor(XSSFTestDataSamples.getSampleFile(file))) {
  687. assertNotNull(extractor);
  688. extractor.getText();
  689. } finally {
  690. ZipSecureFile.setMinInflateRatio(minInf);
  691. }
  692. }
  693. @Test
  694. void zipBombCheckSizesWithinLimits() throws IOException, EncryptedDocumentException {
  695. getZipStatsAndConsume((max_size, min_ratio) -> {
  696. // use values close to, but within the limits
  697. ZipSecureFile.setMinInflateRatio(min_ratio - 0.002);
  698. assertEquals(min_ratio - 0.002, ZipSecureFile.getMinInflateRatio(), 0.00001);
  699. ZipSecureFile.setMaxEntrySize(max_size + 1);
  700. assertEquals(max_size + 1, ZipSecureFile.getMaxEntrySize());
  701. });
  702. }
  703. @Test
  704. void zipBombCheckSizesRatioTooSmall() {
  705. POIXMLException ex = assertThrows(
  706. POIXMLException.class,
  707. () -> getZipStatsAndConsume((max_size, min_ratio) -> {
  708. // check ratio out of bounds
  709. ZipSecureFile.setMinInflateRatio(min_ratio+0.002);
  710. })
  711. );
  712. assertTrue(ex.getMessage().contains("You can adjust this limit via ZipSecureFile.setMinInflateRatio()"));
  713. }
  714. @Test
  715. void zipBombCheckSizesSizeTooBig() throws EncryptedDocumentException {
  716. POIXMLException ex = assertThrows(
  717. POIXMLException.class,
  718. () -> getZipStatsAndConsume((max_size, min_ratio) -> {
  719. // check max entry size ouf of bounds
  720. ZipSecureFile.setMinInflateRatio(min_ratio-0.002);
  721. ZipSecureFile.setMaxEntrySize(max_size-200);
  722. })
  723. );
  724. assertTrue(ex.getMessage().contains("You can adjust this limit via ZipSecureFile.setMaxEntrySize()"));
  725. }
  726. private void getZipStatsAndConsume(BiConsumer<Long,Double> ratioCon) throws IOException {
  727. // use a test file with a xml file bigger than 100k (ZipArchiveThresholdInputStream.GRACE_ENTRY_SIZE)
  728. final File file = XSSFTestDataSamples.getSampleFile("poc-shared-strings.xlsx");
  729. double min_ratio = Double.MAX_VALUE;
  730. long max_size = 0;
  731. try (ZipFile zf = ZipHelper.openZipFile(file)) {
  732. assertNotNull(zf);
  733. Enumeration<? extends ZipArchiveEntry> entries = zf.getEntries();
  734. while (entries.hasMoreElements()) {
  735. ZipArchiveEntry ze = entries.nextElement();
  736. if (ze.getSize() == 0) {
  737. continue;
  738. }
  739. // add zip entry header ~ 128 bytes
  740. long size = ze.getSize()+128;
  741. double ratio = ze.getCompressedSize() / (double)size;
  742. min_ratio = Math.min(min_ratio, ratio);
  743. max_size = Math.max(max_size, size);
  744. }
  745. }
  746. ratioCon.accept(max_size, min_ratio);
  747. //noinspection EmptyTryBlock,unused
  748. try (Workbook wb = WorkbookFactory.create(file, null, true)) {
  749. } finally {
  750. // reset otherwise a lot of ooxml tests will fail
  751. ZipSecureFile.setMinInflateRatio(0.01d);
  752. ZipSecureFile.setMaxEntrySize(0xFFFFFFFFL);
  753. }
  754. }
  755. @Test
  756. void testConstructors() throws IOException {
  757. // verify the various ways to construct a ZipSecureFile
  758. File file = getSampleFile("sample.xlsx");
  759. try (ZipSecureFile zipFile = new ZipSecureFile(file)) {
  760. assertNotNull(zipFile.getName());
  761. }
  762. try (ZipSecureFile zipFile = new ZipSecureFile(file.getAbsolutePath())) {
  763. assertNotNull(zipFile.getName());
  764. }
  765. }
  766. @Test
  767. void testMaxTextSize() {
  768. long before = ZipSecureFile.getMaxTextSize();
  769. try {
  770. ZipSecureFile.setMaxTextSize(12345);
  771. assertEquals(12345, ZipSecureFile.getMaxTextSize());
  772. } finally {
  773. ZipSecureFile.setMaxTextSize(before);
  774. }
  775. }
  776. // bug 60128
  777. @Test
  778. void testCorruptFile() {
  779. File file = getSampleFile("invalid.xlsx");
  780. assertThrows(NotOfficeXmlFileException.class, () -> OPCPackage.open(file, PackageAccess.READ));
  781. }
  782. private interface CountingStream {
  783. InputStream create(InputStream is, int length);
  784. }
  785. // bug 61381
  786. @Test
  787. void testTooShortFilterStreams() throws IOException {
  788. for (String file : new String[]{"sample.xlsx","SampleSS.xls"}) {
  789. for (CountingStream cs : new CountingStream[]{PushbackInputStream::new, BufferedInputStream::new}) {
  790. try (InputStream is = cs.create(xlsSamples.openResourceAsStream(file), 2);
  791. Workbook wb = WorkbookFactory.create(is)) {
  792. assertEquals(3, wb.getNumberOfSheets());
  793. }
  794. }
  795. }
  796. }
  797. @Test
  798. void testBug56479() throws Exception {
  799. try (InputStream is = openSampleStream("dcterms_bug_56479.zip");
  800. OPCPackage p = OPCPackage.open(is)) {
  801. // Check we found the contents of it
  802. boolean foundCoreProps = false, foundDocument = false, foundTheme1 = false;
  803. for (final PackagePart part : p.getParts()) {
  804. final String partName = part.getPartName().toString();
  805. final String contentType = part.getContentType();
  806. switch (partName) {
  807. case "/docProps/core.xml":
  808. assertEquals(ContentTypes.CORE_PROPERTIES_PART, contentType);
  809. foundCoreProps = true;
  810. break;
  811. case "/word/document.xml":
  812. assertEquals(XWPFRelation.DOCUMENT.getContentType(), contentType);
  813. foundDocument = true;
  814. break;
  815. case "/word/theme/theme1.xml":
  816. assertEquals(XWPFRelation.THEME.getContentType(), contentType);
  817. foundTheme1 = true;
  818. break;
  819. }
  820. }
  821. assertTrue(foundCoreProps, "Core not found in " + p.getParts());
  822. assertFalse(foundDocument, "Document should not be found in " + p.getParts());
  823. assertFalse(foundTheme1, "Theme1 should not found in " + p.getParts());
  824. }
  825. }
  826. @Test
  827. void unparseableCentralDirectory() throws IOException {
  828. File f = getSampleFile("at.pzp.www_uploads_media_PP_Scheinecker-jdk6error.pptx");
  829. try (SlideShow<?,?> ppt = SlideShowFactory.create(f, null, true)) {
  830. assertNotNull(ppt);
  831. assertNotNull(ppt.getSlides().get(0));
  832. }
  833. }
  834. @Test
  835. void testClosingStreamOnException() throws IOException {
  836. File tmp = File.createTempFile("poi-test-truncated-zip", "");
  837. // create a corrupted zip file by truncating a valid zip file to the first 100 bytes
  838. try (InputStream is = openSampleStream("dcterms_bug_56479.zip");
  839. OutputStream os = new FileOutputStream(tmp)) {
  840. IOUtils.copy(is, os, 100);
  841. }
  842. // feed the corrupted zip file to OPCPackage
  843. // expected: the zip file is invalid
  844. // this test does not care if open() throws an exception or not.
  845. assertThrows(Exception.class, () -> OPCPackage.open(tmp, PackageAccess.READ));
  846. // If the stream is not closed on exception, it will keep a file descriptor to tmp,
  847. // and requests to the OS to delete the file will fail.
  848. assertTrue(tmp.delete(), "Can't delete tmp file");
  849. }
  850. /**
  851. * If ZipPackage is passed an invalid file, a call to close
  852. * (eg from the OPCPackage open method) should tidy up the
  853. * stream / file the broken file is being read from.
  854. * See bug #60128 for more
  855. */
  856. @Test
  857. void testTidyStreamOnInvalidFile1() throws Exception {
  858. openInvalidFile("SampleSS.ods", false);
  859. }
  860. @Test
  861. void testTidyStreamOnInvalidFile2() throws Exception {
  862. openInvalidFile("SampleSS.ods", true);
  863. }
  864. @Test
  865. void testTidyStreamOnInvalidFile3() throws Exception {
  866. openInvalidFile("SampleSS.txt", false);
  867. }
  868. @Test
  869. void testTidyStreamOnInvalidFile4() throws Exception {
  870. openInvalidFile("SampleSS.txt", true);
  871. }
  872. @Test
  873. void testBug62592() throws Exception {
  874. try (InputStream is = openSampleStream("62592.thmx")) {
  875. assertThrows(InvalidFormatException.class, () -> OPCPackage.open(is));
  876. }
  877. }
  878. @Test
  879. void testBug62592SequentialCallsToGetParts() throws Exception {
  880. //make absolutely certain that sequential calls don't throw InvalidFormatExceptions
  881. String originalFile = getSampleFileName("TestPackageCommon.docx");
  882. try (OPCPackage p2 = OPCPackage.open(originalFile, PackageAccess.READ)) {
  883. assertDoesNotThrow(p2::getParts);
  884. assertDoesNotThrow(p2::getParts);
  885. }
  886. }
  887. @Test
  888. void testDoNotCloseStream() throws IOException {
  889. // up to JDK 10 we did use Mockito here, but OutputStream is
  890. // an abstract class and fails mocking with some changes in JDK 11
  891. // so we use a simple empty output stream implementation instead
  892. OutputStream os = new OutputStream() {
  893. @Override
  894. public void write(int b) {
  895. }
  896. @Override
  897. public void close() {
  898. fail("close should not be called here");
  899. }
  900. };
  901. try (XSSFWorkbook wb = new XSSFWorkbook()) {
  902. wb.createSheet();
  903. wb.write(os);
  904. }
  905. try (SXSSFWorkbook wb = new SXSSFWorkbook()) {
  906. wb.createSheet();
  907. wb.write(os);
  908. }
  909. }
  910. private static void openInvalidFile(final String name, final boolean useStream) throws IOException {
  911. ZipPackage[] pkgTest = { null };
  912. try (final InputStream is = (useStream) ? xlsSamples.openResourceAsStream(name) : null) {
  913. assertThrows(NotOfficeXmlFileException.class, () -> {
  914. try (final ZipPackage pkg = (useStream)
  915. ? new ZipPackage(is, PackageAccess.READ)
  916. : new ZipPackage(xlsSamples.getFile(name), PackageAccess.READ)) {
  917. pkgTest[0] = pkg;
  918. assertNotNull(pkg.getZipArchive());
  919. assertFalse(pkg.getZipArchive().isClosed());
  920. pkg.getParts();
  921. }
  922. });
  923. } finally {
  924. if (pkgTest[0] != null) {
  925. assertNotNull(pkgTest[0].getZipArchive());
  926. assertTrue(pkgTest[0].getZipArchive().isClosed());
  927. }
  928. }
  929. }
  930. @SuppressWarnings("UnstableApiUsage")
  931. @Test
  932. void testBug63029() throws Exception {
  933. File testFile = getSampleFile("sample.docx");
  934. File tmpFile = getOutputFile("Bug63029.docx");
  935. Files.copy(testFile, tmpFile);
  936. int numPartsBefore = 0;
  937. String md5Before = Files.asByteSource(tmpFile).hash(Hashing.sha256()).toString();
  938. try(OPCPackage pkg = OPCPackage.open(tmpFile, PackageAccess.READ_WRITE))
  939. {
  940. numPartsBefore = pkg.getParts().size();
  941. // add a marshaller that will throw an exception on save
  942. pkg.addMarshaller("poi/junit", (part, out) -> {
  943. throw new RuntimeException("Bugzilla 63029");
  944. });
  945. pkg.createPart(createPartName("/poi/test.xml"), "poi/junit");
  946. RuntimeException ex = assertThrows(RuntimeException.class, pkg::close);
  947. // verify there was an exception while closing the file
  948. assertEquals("Fail to save: an error occurs while saving the package : Bugzilla 63029", ex.getMessage());
  949. }
  950. // assert that md5 after closing is the same, i.e. the source is left intact
  951. String md5After = Files.asByteSource(tmpFile).hash(Hashing.sha256()).toString();
  952. assertEquals(md5Before, md5After);
  953. // try to read the source file once again
  954. try ( OPCPackage pkg = OPCPackage.open(tmpFile, PackageAccess.READ_WRITE)){
  955. // the source is still a valid zip archive.
  956. // prior to the fix this used to throw NotOfficeXmlFileException("archive is not a ZIP archive")
  957. // assert that the number of parts remained the same
  958. assertEquals(pkg.getParts().size(), numPartsBefore);
  959. }
  960. }
  961. }