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 40KB

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