diff options
41 files changed, 631 insertions, 108 deletions
diff --git a/build.gradle b/build.gradle index 83ae193dd5..13ea2c604f 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ plugins { id "com.github.spotbugs" version '6.0.27' id 'de.thetaphi.forbiddenapis' version '3.8' id 'org.sonarqube' version '4.0.0.2929' - id 'org.cyclonedx.bom' version '1.10.0' + id 'org.cyclonedx.bom' version '2.0.0' id 'com.adarshr.test-logger' version '3.2.0' } @@ -100,7 +100,7 @@ allprojects { // apply plugin: 'eclipse' apply plugin: 'idea' - version = '5.4.0' + version = '5.4.1-SNAPSHOT' } /** @@ -118,8 +118,8 @@ subprojects { apply plugin: 'com.adarshr.test-logger' ext { - bouncyCastleVersion = '1.79' - commonsCodecVersion = '1.17.1' + bouncyCastleVersion = '1.80' + commonsCodecVersion = '1.17.2' commonsCompressVersion = '1.27.1' commonsIoVersion = '2.18.0' commonsMathVersion = '3.6.1' @@ -215,7 +215,7 @@ subprojects { if (jdkVersion > 8) addBooleanOption('html5', true) addBooleanOption('Xdoclint:all,-missing', true) links 'https://poi.apache.org/apidocs/dev/' - links 'https://docs.oracle.com/javase/8/docs/api/' + if (jdkVersion >= 23) links 'https://docs.oracle.com/en/java/javase/23/docs/api/' else links 'https://docs.oracle.com/javase/8/docs/api/' links 'https://xmlbeans.apache.org/docs/5.0.0/' links 'https://commons.apache.org/proper/commons-compress/apidocs/' use = true @@ -42,7 +42,7 @@ under the License. <description>The Apache POI project Ant build.</description> - <property name="version.id" value="5.4.0"/> + <property name="version.id" value="5.4.1-SNAPSHOT"/> <property name="release.rc" value=""/> <property environment="env"/> @@ -263,7 +263,7 @@ under the License. <!-- jars in the /lib directory, see the fetch-jars target--> - <dependency prefix="main.commons-codec" artifact="commons-codec:commons-codec:1.17.1" usage="main"/> + <dependency prefix="main.commons-codec" artifact="commons-codec:commons-codec:1.17.2" usage="main"/> <dependency prefix="main.commons-collections4" artifact="org.apache.commons:commons-collections4:4.4" usage="main"/> <dependency prefix="main.commons-math3" artifact="org.apache.commons:commons-math3:3.6.1" usage="main"/> <dependency prefix="main.commons-io" artifact="commons-io:commons-io:2.18.0" usage="main"/> @@ -296,11 +296,11 @@ under the License. <!-- xml signature libs - not part of the distribution --> <dependency prefix="dsig.xmlsec" artifact="org.apache.santuario:xmlsec:3.0.5" usage="ooxml-provided"/> - <dependency prefix="dsig.bouncycastle-prov" artifact="org.bouncycastle:bcprov-jdk18on:1.79" usage="ooxml-provided"/> - <dependency prefix="dsig.bouncycastle-pkix" artifact="org.bouncycastle:bcpkix-jdk18on:1.79" usage="ooxml-provided"/> - <dependency prefix="dsig.bouncycastle-util" artifact="org.bouncycastle:bcutil-jdk18on:1.79" usage="ooxml-provided"/> + <dependency prefix="dsig.bouncycastle-prov" artifact="org.bouncycastle:bcprov-jdk18on:1.80" usage="ooxml-provided"/> + <dependency prefix="dsig.bouncycastle-pkix" artifact="org.bouncycastle:bcpkix-jdk18on:1.80" usage="ooxml-provided"/> + <dependency prefix="dsig.bouncycastle-util" artifact="org.bouncycastle:bcutil-jdk18on:1.80" usage="ooxml-provided"/> <!-- only used for signing the release - not used with the ooxml signatures --> - <dependency prefix="dsig.bouncycastle-bcpg" artifact="org.bouncycastle:bcpg-jdk18on:1.79" usage="util"/> + <dependency prefix="dsig.bouncycastle-bcpg" artifact="org.bouncycastle:bcpg-jdk18on:1.80" usage="util"/> <dependency prefix="ooxml.test.stax2" artifact="org.codehaus.woodstox:stax2-api:4.2.1" usage="ooxml-provided"/> <!-- svg/batik/pdf libs - not part of the distribution - move batik to its own directory because of JPMS module-path issues --> @@ -332,7 +332,7 @@ under the License. <!-- jars in the ooxml-lib directory, see the fetch-ooxml-jars target--> <dependency prefix="ooxml.curvesapi" artifact="com.github.virtuald:curvesapi:1.08" usage="ooxml"/> - <dependency prefix="ooxml.xmlbeans" artifact="org.apache.xmlbeans:xmlbeans:5.2.2" usage="ooxml"/> + <dependency prefix="ooxml.xmlbeans" artifact="org.apache.xmlbeans:xmlbeans:5.3.0" usage="ooxml"/> <dependency prefix="ooxml.commons-compress" artifact="org.apache.commons:commons-compress:1.27.1" usage="ooxml"/> <dependency prefix="ooxml.commons-lang3" artifact="org.apache.commons:commons-lang3:3.12.0" usage="ooxml"/> @@ -689,6 +689,7 @@ under the License. <include name="xmlbeans-4*.jar"/> <include name="xmlbeans-5.0*.jar"/> <include name="xmlbeans-5.1.0.jar"/> + <include name="xmlbeans-5.2.2.jar"/> </fileset> <fileset dir="${basedir}/lib/ooxml-provided"> <include name="bc*-1.6*.jar"/> diff --git a/doap_POI.rdf b/doap_POI.rdf index b3d0b7e2e4..88606eb0bd 100644 --- a/doap_POI.rdf +++ b/doap_POI.rdf @@ -37,6 +37,13 @@ <category rdf:resource="https://projects.apache.org/category/library" /> <release> <Version> + <name>Apache POI 5.4.0</name> + <created>2025-01-08</created> + <revision>5.4.0</revision> + </Version> + </release> + <release> + <Version> <name>Apache POI 5.3.0</name> <created>2024-07-02</created> <revision>5.3.0</revision> diff --git a/legal/NOTICE b/legal/NOTICE index 2ca7e7b6af..d600ff75df 100644 --- a/legal/NOTICE +++ b/legal/NOTICE @@ -1,5 +1,5 @@ Apache POI -Copyright 2003-2024 The Apache Software Foundation +Copyright 2003-2025 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (https://www.apache.org/). diff --git a/osgi/pom.xml b/osgi/pom.xml index 51acd6b66e..c4854f4c12 100644 --- a/osgi/pom.xml +++ b/osgi/pom.xml @@ -24,12 +24,12 @@ <groupId>org.apache.poi</groupId> <artifactId>poi-bundle</artifactId> <packaging>bundle</packaging> - <version>5.4.0</version> + <version>5.4.1-SNAPSHOT</version> <name>Apache POI OSGi bundle</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <poi.version>5.4.0</poi.version> + <poi.version>5.4.1-SNAPSHOT</poi.version> <pax.exam.version>4.14.0</pax.exam.version> </properties> diff --git a/poi-ooxml-lite/missing-xsbs.txt b/poi-ooxml-lite/missing-xsbs.txt index cd5b063e63..49a2c51783 100644 --- a/poi-ooxml-lite/missing-xsbs.txt +++ b/poi-ooxml-lite/missing-xsbs.txt @@ -128,3 +128,4 @@ ststrokelinestylea509type sttargetscreensize4357type stverticalanchor22cctype stwrapside6d02type +wsdrd172doctype
\ No newline at end of file diff --git a/poi-ooxml/build.gradle b/poi-ooxml/build.gradle index 843c21ead5..0ec5fda5d6 100644 --- a/poi-ooxml/build.gradle +++ b/poi-ooxml/build.gradle @@ -233,7 +233,7 @@ javadoc { options { if (jdkVersion > 8) addBooleanOption('html5', true) links 'https://poi.apache.org/apidocs/dev/' - links 'https://docs.oracle.com/javase/8/docs/api/' + if (jdkVersion >= 23) links 'https://docs.oracle.com/en/java/javase/23/docs/api/' else links 'https://docs.oracle.com/javase/8/docs/api/' use = true splitIndex = true source = "1.8" diff --git a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java index a2987a742b..4b487fcc9e 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java +++ b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java @@ -154,7 +154,7 @@ import org.w3c.dom.events.MutationEvent; * <p>To use SignatureInfo and its sibling classes, you'll need to have the following libs * in the classpath:</p> * <ul> - * <li>BouncyCastle bcpkix and bcprov (tested against 1.79)</li> + * <li>BouncyCastle bcpkix and bcprov (tested against 1.80)</li> * <li>Apache Santuario "xmlsec" (tested against 3.0.x)</li> * <li>and log4j-api (tested against 2.22.x)</li> * </ul> diff --git a/poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/section/geometry/EllipticalArcTo.java b/poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/section/geometry/EllipticalArcTo.java index 1dcb6cbdfc..99561a6a7d 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/section/geometry/EllipticalArcTo.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/section/geometry/EllipticalArcTo.java @@ -73,6 +73,10 @@ public class EllipticalArcTo implements GeometryRow { for (CellType cell : row.getCellArray()) { String cellName = cell.getN(); + if (cellName == null) { + throw new POIXMLException("Missing cellName in EllipticalArcTo row"); + } + switch (cellName) { case "X": x = XDGFCell.parseDoubleValue(cell); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFGraphicFrame.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFGraphicFrame.java index b554cdd6a9..4dcc60e77b 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFGraphicFrame.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFGraphicFrame.java @@ -79,7 +79,7 @@ public class XSLFGraphicFrame extends XSLFShape implements GraphicalFrame<XSLFSh } CTPoint2D off = xfrm.getOff(); - if (off == null) { + if (off == null || off.getX() == null || off.getY() == null) { throw new IllegalArgumentException("Could not retrieve Off from the XML object"); } double x = Units.toPoints(POIXMLUnits.parseLength(off.xgetX())); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/XSSFSheetXMLHandler.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/XSSFSheetXMLHandler.java index 5e9a58f511..b5cc39728b 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/XSSFSheetXMLHandler.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/XSSFSheetXMLHandler.java @@ -394,8 +394,12 @@ public class XSSFSheetXMLHandler extends DefaultHandler { // Try to use the value as a formattable number double d = Double.parseDouble(fv); thisStr = formatter.formatRawCellContents(d, this.formatIndex, this.formatString); - } catch (NumberFormatException e) { + } catch (Exception e) { // Formula is a String result not a Numeric one + LOG.atInfo().log( + "Error formatting cell '{}' - will use its raw value instead (format '{}')", + cellRef, + this.formatString); thisStr = fv; } } else { @@ -426,10 +430,20 @@ public class XSSFSheetXMLHandler extends DefaultHandler { case NUMBER: String n = value.toString(); - if (this.formatString != null && n.length() > 0) - thisStr = formatter.formatRawCellContents(Double.parseDouble(n), this.formatIndex, this.formatString); - else + if (this.formatString != null && n.length() > 0) { + try { + thisStr = formatter.formatRawCellContents( + Double.parseDouble(n), this.formatIndex, this.formatString); + } catch (Exception e) { + LOG.atInfo().log( + "Error formatting cell '{}' - will use its raw value instead (format '{}')", + cellRef, + this.formatString); + thisStr = n; + } + } else { thisStr = n; + } break; default: diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/AutoSizeColumnTracker.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/AutoSizeColumnTracker.java index b6b674a53b..de85d9a4d4 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/AutoSizeColumnTracker.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/AutoSizeColumnTracker.java @@ -392,9 +392,9 @@ import org.apache.poi.util.Internal; */ private void updateColumnWidth(final Cell cell, final ColumnWidthPair pair) { final double unmergedWidth = - SheetUtil.getCellWidth(cell, defaultCharWidth, dataFormatter, false) + arbitraryExtraWidth; + SheetUtil.getCellWidth(cell, defaultCharWidth, dataFormatter, false); final double mergedWidth = - SheetUtil.getCellWidth(cell, defaultCharWidth, dataFormatter, true) + arbitraryExtraWidth; + SheetUtil.getCellWidth(cell, defaultCharWidth, dataFormatter, true); pair.setMaxColumnWidths(unmergedWidth, mergedWidth); } } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SXSSFSheet.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SXSSFSheet.java index 08b7fb9c45..fc5462620e 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SXSSFSheet.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SXSSFSheet.java @@ -1666,7 +1666,8 @@ public class SXSSFSheet implements Sheet, OoxmlSheetExtensions { } // get the best-fit width of rows currently in the random access window - final int activeWidth = (int) (256 * SheetUtil.getColumnWidth(this, column, useMergedCells)); + final double w1 = SheetUtil.getColumnWidth(this, column, useMergedCells); + final int activeWidth = (int) ((256 * w1) + getArbitraryExtraWidth()); // the best-fit width for both flushed rows and random access window rows // flushedWidth or activeWidth may be negative if column contains only blank cells diff --git a/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestSXSSFSheet.java b/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestSXSSFSheet.java index 015f608cf2..5ab64a69aa 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestSXSSFSheet.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestSXSSFSheet.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.io.ByteArrayOutputStream; import java.io.IOException; import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; @@ -45,7 +46,7 @@ public final class TestSXSSFSheet extends BaseTestXSheet { @AfterEach - void tearDown(){ + void tearDown() { SXSSFITestDataProvider.instance.cleanup(); } @@ -84,9 +85,9 @@ public final class TestSXSSFSheet extends BaseTestXSheet { } /** - * Bug 35084: cloning cells with formula - * - * The test is disabled because cloning of sheets is not supported in SXSSF + * Bug 35084: cloning cells with formula + * <p> + * The test is disabled because cloning of sheets is not supported in SXSSF */ @Override @Test @@ -193,9 +194,9 @@ public final class TestSXSSFSheet extends BaseTestXSheet { //one level sheet.groupRow(9, 10); - try(UnsynchronizedByteArrayOutputStream bos = UnsynchronizedByteArrayOutputStream.builder().get()) { + try (UnsynchronizedByteArrayOutputStream bos = UnsynchronizedByteArrayOutputStream.builder().get()) { workbook.write(bos); - try(XSSFWorkbook xssfWorkbook = new XSSFWorkbook(bos.toInputStream())) { + try (XSSFWorkbook xssfWorkbook = new XSSFWorkbook(bos.toInputStream())) { XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); CTRow ctrow = xssfSheet.getRow(9).getCTRow(); @@ -223,9 +224,9 @@ public final class TestSXSSFSheet extends BaseTestXSheet { //two level sheet.groupRow(10, 13); - try(UnsynchronizedByteArrayOutputStream bos = UnsynchronizedByteArrayOutputStream.builder().get()) { + try (UnsynchronizedByteArrayOutputStream bos = UnsynchronizedByteArrayOutputStream.builder().get()) { workbook.write(bos); - try(XSSFWorkbook xssfWorkbook = new XSSFWorkbook(bos.toInputStream())) { + try (XSSFWorkbook xssfWorkbook = new XSSFWorkbook(bos.toInputStream())) { XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); CTRow ctrow = xssfSheet.getRow(9).getCTRow(); @@ -242,4 +243,43 @@ public final class TestSXSSFSheet extends BaseTestXSheet { } } } + + @Test + void autosizeWithArbitraryExtraWidth() throws IOException { + final int extra = 100; + final String longText = + "This is a very long text that will exceed default column width for sure."; + int width0, width1 = 0; + try (SXSSFWorkbook workbook0 = new SXSSFWorkbook()) { + SXSSFSheet sheet = workbook0.createSheet(); + sheet.trackColumnForAutoSizing(0); + + SXSSFRow row = sheet.createRow(0); + row.createCell(0).setCellValue(longText); + sheet.autoSizeColumn(0); + + try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + workbook0.write(bos); + } + width0 = sheet.getColumnWidth(0); + } + + try (SXSSFWorkbook workbook1 = new SXSSFWorkbook()) { + SXSSFSheet sheet = workbook1.createSheet(); + sheet.setArbitraryExtraWidth(extra); + sheet.trackColumnForAutoSizing(0); + + SXSSFRow row = sheet.createRow(0); + row.createCell(0).setCellValue(longText); + sheet.autoSizeColumn(0); + + try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + workbook1.write(bos); + } + width1 = sheet.getColumnWidth(0); + } + + assertEquals(width0 + extra, width1); + } + } diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/PropertiesChunk.java b/poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/PropertiesChunk.java index 91692e3897..7b037f3e5b 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/PropertiesChunk.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/PropertiesChunk.java @@ -209,6 +209,12 @@ public abstract class PropertiesChunk extends Chunk { int id = LittleEndian.readUShort(value); long flags = LittleEndian.readUInt(value); + boolean multivalued = false; + if ((typeID & Types.MULTIVALUED_FLAG) != 0) { + typeID -= Types.MULTIVALUED_FLAG; + multivalued = true; + } + // Turn the Type and ID into helper objects MAPIType type = Types.getById(typeID); MAPIProperty prop = MAPIProperty.get(id); @@ -255,7 +261,7 @@ public abstract class PropertiesChunk extends Chunk { // to another chunk which holds the data itself boolean isPointer = false; int length = type.getLength(); - if (type.isPointer()) { + if (type.isPointer() || multivalued) { isPointer = true; length = 8; } diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/HWPFDocument.java b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/HWPFDocument.java index 3c4275b2a9..a4fd555a91 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/HWPFDocument.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/HWPFDocument.java @@ -765,7 +765,8 @@ public final class HWPFDocument extends HWPFDocumentCore { // write out the PAPBinTable. _fib.setFcPlcfbtePapx(tableOffset); - _pbt.writeTo(wordDocumentStream, tableStream, _cft.getTextPieceTable()); + // Right now we don't know how to save dataStream modifications, so we can just pipe them to a black hole. + _pbt.writeTo(wordDocumentStream, tableStream, new ByteArrayOutputStream(), _cft.getTextPieceTable()); _fib.setLcbPlcfbtePapx(tableStream.size() - tableOffset); tableOffset = tableStream.size(); diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/AbstractWordUtils.java b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/AbstractWordUtils.java index dc996af43d..9ef569fc6b 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/AbstractWordUtils.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/AbstractWordUtils.java @@ -240,6 +240,11 @@ public class AbstractWordUtils { } } + // ensure the format does not grow too large, number-format + // can be roman-numbers, where very large numbers would have + // very many "M" and thus may cause memory to overload + IOUtils.safelyAllocateCheck(num, MAX_BULLET_BUFFER_SIZE/10); + bulletBuffer.append( NumberFormatter.getNumber( num, list.getNumberFormat( level ) ) ); } else { diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/model/PAPBinTable.java b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/model/PAPBinTable.java index 32e9ca9a36..225fc97e32 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/model/PAPBinTable.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/model/PAPBinTable.java @@ -366,7 +366,7 @@ public class PAPBinTable } public void writeTo( ByteArrayOutputStream wordDocumentStream, - ByteArrayOutputStream tableStream, CharIndexTranslator translator ) + ByteArrayOutputStream tableStream, ByteArrayOutputStream dataStream, CharIndexTranslator translator ) throws IOException { @@ -401,7 +401,7 @@ public class PAPBinTable PAPFormattedDiskPage pfkp = new PAPFormattedDiskPage(); pfkp.fill(overflow); - byte[] bufFkp = pfkp.toByteArray(tableStream, translator); + byte[] bufFkp = pfkp.toByteArray(dataStream, translator); wordDocumentStream.write(bufFkp); overflow = pfkp.getOverflow(); diff --git a/poi-scratchpad/src/test/java/org/apache/poi/hsmf/TestBasics.java b/poi-scratchpad/src/test/java/org/apache/poi/hsmf/TestBasics.java index 83f59bd789..49dea29aff 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hsmf/TestBasics.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hsmf/TestBasics.java @@ -19,13 +19,20 @@ package org.apache.poi.hsmf; import static org.apache.poi.POITestCase.assertContains; import static org.apache.poi.POITestCase.assertStartsWith; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import org.apache.poi.POIDataSamples; import org.apache.poi.hsmf.datatypes.AttachmentChunks; +import org.apache.poi.hsmf.datatypes.Chunks; import org.apache.poi.hsmf.datatypes.DirectoryChunk; +import org.apache.poi.hsmf.datatypes.MAPIProperty; import org.apache.poi.hsmf.exceptions.ChunkNotFoundException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -273,4 +280,33 @@ public final class TestBasics { } } } + + @Test + void testBug69315() throws Exception { + POIDataSamples testData = POIDataSamples.getPOIFSInstance(); + try (MAPIMessage mapi = new MAPIMessage(testData.openResourceAsStream("MailSentPropertyMultiple.msg"))) { + assertNotNull(mapi.getAttachmentFiles()); + assertNotNull(mapi.getDisplayBCC()); + assertNotNull(mapi.getMessageDate()); + + Chunks chunks = mapi.getMainChunks(); + assertNotNull(chunks); + assertNotNull(chunks.getRawProperties()); + assertNotNull(chunks.getRawProperties().get(MAPIProperty.CLIENT_SUBMIT_TIME)); + + AttachmentChunks[] attachments = mapi.getAttachmentFiles(); + for (AttachmentChunks attachment : attachments) { + DirectoryChunk chunkDirectory = attachment.getAttachmentDirectory(); + if (chunkDirectory != null) { + MAPIMessage attachmentMSG = chunkDirectory.getAsEmbeddedMessage(); + assertNotNull(attachmentMSG); + String body = attachmentMSG.getTextBody(); + assertNotNull(body); + } + } + + assertNull(mapi.getSummaryInformation()); + assertNull(mapi.getDocumentSummaryInformation()); + } + } } diff --git a/poi-scratchpad/src/test/java/org/apache/poi/hwpf/model/TestPAPBinTable.java b/poi-scratchpad/src/test/java/org/apache/poi/hwpf/model/TestPAPBinTable.java index b65843ab63..6588162260 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hwpf/model/TestPAPBinTable.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hwpf/model/TestPAPBinTable.java @@ -59,7 +59,8 @@ public final class TestPAPBinTable { HWPFFileSystem fileSys = new HWPFFileSystem(); ByteArrayOutputStream tableOut = fileSys.getStream( "1Table" ); ByteArrayOutputStream mainOut = fileSys.getStream( "WordDocument" ); - _pAPBinTable.writeTo( mainOut, tableOut, fakeTPT ); + ByteArrayOutputStream dataOut = fileSys.getStream("Data"); + _pAPBinTable.writeTo(mainOut, tableOut, dataOut, fakeTPT); byte[] newTableStream = tableOut.toByteArray(); byte[] newMainStream = mainOut.toByteArray(); diff --git a/poi-scratchpad/src/test/java/org/apache/poi/hwpf/usermodel/TestBugs.java b/poi-scratchpad/src/test/java/org/apache/poi/hwpf/usermodel/TestBugs.java index be98329282..e2e6ce89de 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hwpf/usermodel/TestBugs.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hwpf/usermodel/TestBugs.java @@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -36,6 +37,8 @@ import java.util.Collection; import java.util.List; import org.apache.commons.codec.digest.DigestUtils; +import org.apache.poi.extractor.ExtractorFactory; +import org.apache.poi.extractor.POITextExtractor; import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.hwpf.HWPFOldDocument; import org.apache.poi.hwpf.HWPFTestDataSamples; @@ -314,64 +317,66 @@ class TestBugs { * CharacterRun.replaceText() */ @Test - void test47287() { - HWPFDocument doc = openSampleFile("Bug47287.doc"); - String[] values = {"1-1", "1-2", "1-3", "1-4", "1-5", "1-6", "1-7", - "1-8", "1-9", "1-10", "1-11", "1-12", "1-13", "1-14", "1-15",}; - int usedVal = 0; - String PLACEHOLDER = "\u2002\u2002\u2002\u2002\u2002"; - Range r = doc.getRange(); - for (int x = 0; x < r.numSections(); x++) { - Section s = r.getSection(x); - for (int y = 0; y < s.numParagraphs(); y++) { - Paragraph p = s.getParagraph(y); - - for (int z = 0; z < p.numCharacterRuns(); z++) { - boolean isFound = false; - - // character run - CharacterRun run = p.getCharacterRun(z); - // character run text - String text = run.text(); - String oldText = text; - int c = text.indexOf("FORMTEXT "); - if (c < 0) { - int k = text.indexOf(PLACEHOLDER); - if (k >= 0) { - text = text.substring(0, k) + values[usedVal] - + text.substring(k + PLACEHOLDER.length()); - usedVal++; - isFound = true; - } - } else { - for (; c >= 0; c = text.indexOf("FORMTEXT ", c - + "FORMTEXT ".length())) { - int k = text.indexOf(PLACEHOLDER, c); + void test47287() throws IOException { + try (HWPFDocument doc = openSampleFile("Bug47287.doc")) { + String[] values = { "1-1", "1-2", "1-3", "1-4", "1-5", "1-6", "1-7", + "1-8", "1-9", "1-10", "1-11", "1-12", "1-13", "1-14", "1-15", + }; + int usedVal = 0; + String PLACEHOLDER = "\u2002\u2002\u2002\u2002\u2002"; + Range r = doc.getRange(); + for (int x = 0; x < r.numSections(); x++) { + Section s = r.getSection(x); + for (int y = 0; y < s.numParagraphs(); y++) { + Paragraph p = s.getParagraph(y); + + for (int z = 0; z < p.numCharacterRuns(); z++) { + boolean isFound = false; + + // character run + CharacterRun run = p.getCharacterRun(z); + // character run text + String text = run.text(); + String oldText = text; + int c = text.indexOf("FORMTEXT "); + if (c < 0) { + int k = text.indexOf(PLACEHOLDER); if (k >= 0) { - text = text.substring(0, k) - + values[usedVal] - + text.substring(k - + PLACEHOLDER.length()); + text = text.substring(0, k) + values[usedVal] + + text.substring(k + PLACEHOLDER.length()); usedVal++; isFound = true; } + } else { + for (; c >= 0; c = text.indexOf("FORMTEXT ", c + + "FORMTEXT ".length())) { + int k = text.indexOf(PLACEHOLDER, c); + if (k >= 0) { + text = text.substring(0, k) + + values[usedVal] + + text.substring(k + + PLACEHOLDER.length()); + usedVal++; + isFound = true; + } + } + } + if (isFound) { + run.replaceText(oldText, text, 0); } - } - if (isFound) { - run.replaceText(oldText, text, 0); - } + } } } - } - String docText = r.text(); + String docText = r.text(); - assertContains(docText, "1-1"); - assertContains(docText, "1-12"); + assertContains(docText, "1-1"); + assertContains(docText, "1-12"); - assertNotContained(docText, "1-13"); - assertNotContained(docText, "1-15"); + assertNotContained(docText, "1-13"); + assertNotContained(docText, "1-15"); + } } /** @@ -718,10 +723,13 @@ class TestBugs { assertEquals(section2NumColumns, section.getNumColumns()); } + /** + * [RESOLVED FIXED] Bug 57603 - failed to create Word 2003 with seven or more columns + */ @Test void test57603SevenRowTable() throws Exception { try (HWPFDocument hwpfDocument = openSampleFile("57603-seven_columns.doc")) { - assertThrows(ArrayIndexOutOfBoundsException.class, () -> HWPFTestDataSamples.writeOutAndReadBack(hwpfDocument)); + HWPFTestDataSamples.writeOutAndReadBack(hwpfDocument); } } @@ -780,4 +788,26 @@ class TestBugs { assertNotNull(pictures); } } + + // + @Test + void test58805() throws IOException { + try (HWPFDocument doc = openSampleFile("header_footer_replace.doc")) { + + Range oRange = doc.getHeaderStoryRange(); + for (int i = 0; i < oRange.numCharacterRuns(); i++) { + CharacterRun run = oRange.getCharacterRun(i); + run.replaceText("_TEST_", "This text is longer than the initial text. It goes on and on without interruption."); + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + doc.write(out); + out.flush(); + + POITextExtractor extractor = ExtractorFactory.createExtractor(new ByteArrayInputStream(out.toByteArray())); + assertThrows(IllegalArgumentException.class, + () -> /*String text =*/ extractor.getText()); + // assertFalse(text.contains("_TEST_"), "Had: " + text); + } + } } diff --git a/poi/src/main/java/org/apache/poi/common/usermodel/HyperlinkType.java b/poi/src/main/java/org/apache/poi/common/usermodel/HyperlinkType.java index 6152a85e45..d38fa16c88 100644 --- a/poi/src/main/java/org/apache/poi/common/usermodel/HyperlinkType.java +++ b/poi/src/main/java/org/apache/poi/common/usermodel/HyperlinkType.java @@ -25,7 +25,7 @@ public enum HyperlinkType { /** Not a hyperlink */ @Internal NONE(-1), - + /** * Link to an existing file or web page */ @@ -37,7 +37,10 @@ public enum HyperlinkType { DOCUMENT(2), /** - * Link to an E-mail address + * Link to an E-mail address. + * + * Please note that this currently only works if the address in the hyperlink + * uses the prefix "mailto:" as the binary formats do not persis this type. */ EMAIL(3), @@ -45,13 +48,13 @@ public enum HyperlinkType { * Link to a file */ FILE(4); - - + + /** @deprecated POI 3.15 beta 3 */ @Internal(since="3.15 beta 3") @Deprecated private final int code; - + /** * The codes don't have any real meaning. * They are bytes that are read in and written out from HSSF, HSLF, XSSF, and XSLF are different @@ -66,7 +69,7 @@ public enum HyperlinkType { HyperlinkType(int code) { this.code = code; } - + /** * @deprecated POI 3.15 beta 3 * diff --git a/poi/src/main/java/org/apache/poi/hssf/record/RecordFactory.java b/poi/src/main/java/org/apache/poi/hssf/record/RecordFactory.java index 16f1a04d04..08b49f5be4 100644 --- a/poi/src/main/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/poi/src/main/java/org/apache/poi/hssf/record/RecordFactory.java @@ -34,8 +34,8 @@ import org.apache.poi.util.RecordFormatException; public final class RecordFactory { private static final int NUM_RECORDS = 512; - // how many records we read at max by default (can be adjusted via IOUtils) - //increased to 5 million due to https://bz.apache.org/bugzilla/show_bug.cgi?id=65887 + // how many records we read at max by default (can be adjusted via the static setters) + // increased to 5 million due to https://bz.apache.org/bugzilla/show_bug.cgi?id=65887 private static final int DEFAULT_MAX_NUMBER_OF_RECORDS = 5_000_000; private static int MAX_NUMBER_OF_RECORDS = DEFAULT_MAX_NUMBER_OF_RECORDS; diff --git a/poi/src/main/java/org/apache/poi/hssf/record/RecordInputStream.java b/poi/src/main/java/org/apache/poi/hssf/record/RecordInputStream.java index 06420f394b..561768f32b 100644 --- a/poi/src/main/java/org/apache/poi/hssf/record/RecordInputStream.java +++ b/poi/src/main/java/org/apache/poi/hssf/record/RecordInputStream.java @@ -216,7 +216,8 @@ public final class RecordInputStream implements LittleEndianInput { _currentDataLength = _bhi.readDataSize(); if (_currentDataLength > MAX_RECORD_DATA_SIZE) { throw new RecordFormatException("The content of an excel record cannot exceed " - + MAX_RECORD_DATA_SIZE + " bytes"); + + MAX_RECORD_DATA_SIZE + " bytes, but had: " + _currentDataLength + + " for record with sid: " + _currentSid); } } diff --git a/poi/src/main/java/org/apache/poi/sl/extractor/SlideShowExtractor.java b/poi/src/main/java/org/apache/poi/sl/extractor/SlideShowExtractor.java index 1b1480caa6..7579f8a9f8 100644 --- a/poi/src/main/java/org/apache/poi/sl/extractor/SlideShowExtractor.java +++ b/poi/src/main/java/org/apache/poi/sl/extractor/SlideShowExtractor.java @@ -364,6 +364,10 @@ public class SlideShowExtractor< // PowerPoint seems to store files with \r as the line break // The messes things up on everything but a Mac, so translate them to \n String txt = tr.getRawText(); + if (txt == null) { + return ""; + } + txt = txt.replace('\r', '\n'); txt = txt.replace((char) 0x0B, sep); diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/TextFunction.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/TextFunction.java index 518fe08e0c..5a2b613c93 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/functions/TextFunction.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/TextFunction.java @@ -108,12 +108,14 @@ public abstract class TextFunction implements Function { return new NumberEval(arg.length()); } }; + public static final Function LOWER = new SingleArgTextFunc() { @Override protected ValueEval evaluate(String arg) { return new StringEval(arg.toLowerCase(Locale.ROOT)); } }; + public static final Function UPPER = new SingleArgTextFunc() { @Override protected ValueEval evaluate(String arg) { @@ -246,13 +248,16 @@ public abstract class TextFunction implements Function { private static final class LeftRight extends Var1or2ArgFunction { private static final ValueEval DEFAULT_ARG1 = new NumberEval(1.0); private final boolean _isLeft; + protected LeftRight(boolean isLeft) { _isLeft = isLeft; } + @Override public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { return evaluate(srcRowIndex, srcColumnIndex, arg0, DEFAULT_ARG1); } + @Override public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { @@ -369,7 +374,6 @@ public abstract class TextFunction implements Function { try { valueDouble = DateUtil.parseDateTime(evaluated); } catch (Exception ignored) { - valueDouble = null; } } } @@ -393,7 +397,7 @@ public abstract class TextFunction implements Function { * Using it instead of {@link OperandResolver#coerceValueToString(ValueEval)} in order to handle booleans differently. */ private String formatPatternValueEval2String(ValueEval ve) { - String format = null; + final String format; if (!(ve instanceof BoolEval) && (ve instanceof StringValueEval)) { StringValueEval sve = (StringValueEval) ve; format = sve.getStringValue(); @@ -414,6 +418,7 @@ public abstract class TextFunction implements Function { public SearchFind(boolean isCaseSensitive) { _isCaseSensitive = isCaseSensitive; } + @Override public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { try { @@ -424,6 +429,7 @@ public abstract class TextFunction implements Function { return e.getErrorEval(); } } + @Override public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, ValueEval arg2) { @@ -440,6 +446,7 @@ public abstract class TextFunction implements Function { return e.getErrorEval(); } } + private ValueEval eval(String haystack, String needle, int startIndex) { int result; if (_isCaseSensitive) { @@ -454,6 +461,7 @@ public abstract class TextFunction implements Function { return new NumberEval(result + 1.); } } + /** * Implementation of the FIND() function.<p> * @@ -468,6 +476,7 @@ public abstract class TextFunction implements Function { * Author: Torstein Tauno Svendsen (torstei@officenet.no) */ public static final Function FIND = new SearchFind(true); + /** * Implementation of the FIND() function.<p> * diff --git a/poi/src/main/java/org/apache/poi/ss/usermodel/DataFormatter.java b/poi/src/main/java/org/apache/poi/ss/usermodel/DataFormatter.java index b3de0462b0..bfe795025e 100644 --- a/poi/src/main/java/org/apache/poi/ss/usermodel/DataFormatter.java +++ b/poi/src/main/java/org/apache/poi/ss/usermodel/DataFormatter.java @@ -163,6 +163,11 @@ public class DataFormatter { private static final Pattern alternateGrouping = Pattern.compile("([#0]([^.#0])[#0]{3})"); /** + * For handling '0#' properly + */ + private static final Pattern decimalFormatFix = Pattern.compile("0+#"); + + /** * Cells formatted with a date or time format and which contain invalid date or time values * show 255 pound signs ("#"). */ @@ -380,7 +385,7 @@ public class DataFormatter { // String formatStr = (i < formatBits.length) ? formatBits[i] : formatBits[0]; // this replace is done to fix https://bz.apache.org/bugzilla/show_bug.cgi?id=63211 - String formatStr = formatStrIn.replace("\\%", "\'%\'"); + String formatStr = formatStrIn.replace("\\%", "'%'"); // Excel supports 2+ part conditional data formats, eg positive/negative/zero, // or (>1000),(>0),(0),(negative). As Java doesn't handle these kinds @@ -700,7 +705,7 @@ public class DataFormatter { private String cleanFormatForNumber(String formatStrIn) { // this replace is done to fix https://bz.apache.org/bugzilla/show_bug.cgi?id=63211 - String formatStr = formatStrIn.replace("\\%", "\'%\'"); + String formatStr = formatStrIn.replace("\\%", "'%'"); StringBuilder sb = new StringBuilder(formatStr); @@ -850,6 +855,11 @@ public class DataFormatter { } } + // Excel ignores leading zeros, but Java fails with an exception below + if (decimalFormatFix.matcher(format).matches()) { + format = "#"; + } + try { return new InternalDecimalFormatWithScale(format, symbols); } catch(IllegalArgumentException iae) { diff --git a/poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java b/poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java index bead9cef3c..0f44be4279 100644 --- a/poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java +++ b/poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java @@ -78,8 +78,8 @@ public class DateUtil { private static final Pattern date_ptrn5 = Pattern.compile("^\\[DBNum([123])]"); private static final DateTimeFormatter dateTimeFormats = new DateTimeFormatterBuilder() - .appendPattern("[dd MMM[ yyyy]][[ ]h:m[:s][.SSS] a][[ ]H:m[:s][.SSS]]") - .appendPattern("[[yyyy ]dd-MMM[-yyyy]][[ ]h:m[:s][.SSS] a][[ ]H:m[:s][.SSS]]") + .appendPattern("[d[.] [MMMM][MMM][ yyyy]][[ ]h:m[:s][.SSS] a][[ ]H:m[:s][.SSS]]") + .appendPattern("[[yyyy ]d-[MMMM][MMM][-yyyy]][[ ]h:m[:s][.SSS] a][[ ]H:m[:s][.SSS]]") .appendPattern("[M/dd[/yyyy]][[ ]h:m[:s][.SSS] a][[ ]H:m[:s][.SSS]]") .appendPattern("[[yyyy/]M/dd][[ ]h:m[:s][.SSS] a][[ ]H:m[:s][.SSS]]") .parseDefaulting(ChronoField.YEAR_OF_ERA, LocaleUtil.getLocaleCalendar().get(Calendar.YEAR)) diff --git a/poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java b/poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java index 26793e1930..a44cce0e42 100644 --- a/poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java +++ b/poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java @@ -19,7 +19,8 @@ package org.apache.poi.hssf.record; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -27,13 +28,17 @@ import java.io.InputStream; import java.util.List; import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; +import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.util.HexRead; +import org.apache.poi.util.RecordFormatException; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; /** * Tests the record factory */ +@Isolated // changes static values, so other tests should not run at the same time final class TestRecordFactory { @@ -178,11 +183,11 @@ final class TestRecordFactory { List<org.apache.poi.hssf.record.Record> records = RecordFactory.createRecords(new ByteArrayInputStream(data)); assertEquals(5, records.size()); - assertTrue(records.get(0) instanceof ObjRecord); - assertTrue(records.get(1) instanceof DrawingRecord); - assertTrue(records.get(2) instanceof TextObjectRecord); - assertTrue(records.get(3) instanceof ContinueRecord); - assertTrue(records.get(4) instanceof ObjRecord); + assertInstanceOf(ObjRecord.class, records.get(0)); + assertInstanceOf(DrawingRecord.class, records.get(1)); + assertInstanceOf(TextObjectRecord.class, records.get(2)); + assertInstanceOf(ContinueRecord.class, records.get(3)); + assertInstanceOf(ObjRecord.class, records.get(4)); //serialize and verify that the serialized data is the same as the original UnsynchronizedByteArrayOutputStream out = UnsynchronizedByteArrayOutputStream.builder().get(); @@ -231,4 +236,22 @@ final class TestRecordFactory { assertEquals(5, outRecs.size()); } } + + @Test + void testMaxNumberOfRecords() { + int prev = RecordFactory.getMaxNumberOfRecords(); + + try { + // check setter/getter + RecordFactory.setMaxNumberOfRecords(0); + assertEquals(0, RecordFactory.getMaxNumberOfRecords()); + + // check exception when exceeding the limit + //noinspection resource + assertThrows(RecordFormatException.class, + () -> HSSFTestDataSamples.openSampleWorkbook("SampleSS.xls")); + } finally { + RecordFactory.setMaxNumberOfRecords(prev); + } + } } diff --git a/poi/src/test/java/org/apache/poi/ss/formula/eval/TestFormulaBugs.java b/poi/src/test/java/org/apache/poi/ss/formula/eval/TestFormulaBugs.java index 1e03fa8621..6692fc81b4 100644 --- a/poi/src/test/java/org/apache/poi/ss/formula/eval/TestFormulaBugs.java +++ b/poi/src/test/java/org/apache/poi/ss/formula/eval/TestFormulaBugs.java @@ -243,4 +243,30 @@ final class TestFormulaBugs { CellValue value = evaluateFormulaInCell(wb, cell, formula); assertEquals(expectedValue, value.getStringValue()); } + + @Test + public void testFormula_58571() throws IOException { + try (Workbook wb = new HSSFWorkbook()) { + FormulaEvaluator eval = wb.getCreationHelper().createFormulaEvaluator(); + + Sheet sheet = wb.createSheet("test"); + Row row = sheet.createRow(0); + Cell cell = row.createCell(0); + // it seems the two double-quotes are replaced with one double-quote when parsing the function + // this does not work: cell.setCellFormula("TEXT(B1, \"h\"\"h\"\" m\"\"m\"\"\")"); + cell.setCellFormula("TEXT(B1, \"h\"\"\"\"h\"\"\"\" m\"\"\"\"m\"\"\"\"\")"); + + Cell cellValue = row.createCell(1); + cellValue.setCellValue(0.0104166666666666); + + CellValue value = eval.evaluate(cell); + assertEquals(CellType.STRING, value.getCellType()); + assertEquals("0h 15m", value.getStringValue()); + + cellValue.setCellValue(1.123); + value = eval.evaluate(cell); + assertEquals(CellType.STRING, value.getCellType()); + assertEquals("0h 15m", value.getStringValue()); + } + } } diff --git a/poi/src/test/java/org/apache/poi/ss/formula/functions/TestValue.java b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestValue.java index 33d1e7bfa4..a5d6e8948b 100644 --- a/poi/src/test/java/org/apache/poi/ss/formula/functions/TestValue.java +++ b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestValue.java @@ -32,6 +32,9 @@ import org.apache.poi.ss.util.Utils; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Locale; /** * Tests for {@link Value} @@ -86,6 +89,14 @@ final class TestValue { } @Test + void testDates() { + confirmValue("1 January 2025", 45658); + confirmValue("01 January 2025", 45658); + confirmValue("1 Jan 2025", 45658); + confirmValue("01 Jan 2025", 45658); + } + + @Test void testErrors() { confirmValueError("1+1"); confirmValueError("1 1"); diff --git a/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestHyperlink.java b/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestHyperlink.java index e5ef5312d9..76f11890c0 100644 --- a/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestHyperlink.java +++ b/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestHyperlink.java @@ -21,11 +21,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; import org.apache.poi.common.usermodel.HyperlinkType; import org.apache.poi.ss.ITestDataProvider; +import org.apache.poi.ss.SpreadsheetVersion; import org.junit.jupiter.api.Test; /** @@ -142,5 +145,119 @@ public abstract class BaseTestHyperlink { wb.close(); } + @Test + void testHyperlinkEmailType69265_https() throws IOException { + boolean isHSSF = _testDataProvider.getSpreadsheetVersion() == SpreadsheetVersion.EXCEL97; + + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + try (Workbook workbook = _testDataProvider.createWorkbook()) { + Sheet sheet = workbook.createSheet("Hyperlink Example"); + + Row row = sheet.createRow(0); + Cell cell = row.createCell(0); + + // Create a hyperlink + CreationHelper createHelper = workbook.getCreationHelper(); + Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.EMAIL); + hyperlink.setLabel("mylabel"); + hyperlink.setAddress("https://www.example.com"); + + // Set the label and the hyperlink + cell.setCellValue("Click here"); + cell.setHyperlink(hyperlink); + + // Get the cell value and hyperlink address + assertEquals("Click here", cell.getStringCellValue()); + Hyperlink cellHyperlink = cell.getHyperlink(); + assertEquals("https://www.example.com", cellHyperlink.getAddress()); + + // HSSF does not support Email, thus falls back to URL, HSSF also uses a hardcoded "label" + assertEquals( + isHSSF ? HyperlinkType.URL : HyperlinkType.EMAIL, + cellHyperlink.getType()); + assertEquals( + isHSSF ? "url" : "mylabel", + cellHyperlink.getLabel()); + + workbook.write(out); + } + + out.flush(); + + try (Workbook wbBack = WorkbookFactory.create(new ByteArrayInputStream(out.toByteArray()))) { + Sheet sheet = wbBack.getSheet("Hyperlink Example"); + Row row = sheet.getRow(0); + Cell cell = row.getCell(0); + + Hyperlink hyperlink = cell.getHyperlink(); + + // when not using "mailto:", it is reverted back to URL currently + assertEquals( + HyperlinkType.URL, + hyperlink.getType()); + assertEquals( + isHSSF ? "url" : "mylabel", + hyperlink.getLabel()); + } + } + } + + @Test + void testHyperlinkEmailType69265_mailto() throws IOException { + boolean isHSSF = _testDataProvider.getSpreadsheetVersion() == SpreadsheetVersion.EXCEL97; + + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + try (Workbook workbook = _testDataProvider.createWorkbook()) { + Sheet sheet = workbook.createSheet("Hyperlink Example"); + + Row row = sheet.createRow(0); + Cell cell = row.createCell(0); + + // Create a hyperlink + CreationHelper createHelper = workbook.getCreationHelper(); + Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.EMAIL); + hyperlink.setLabel("mylabel"); + hyperlink.setAddress("mailto://www.example.com"); + + // Set the label and the hyperlink + cell.setCellValue("Click here"); + cell.setHyperlink(hyperlink); + + // Get the cell value and hyperlink address + assertEquals("Click here", cell.getStringCellValue()); + Hyperlink cellHyperlink = cell.getHyperlink(); + assertEquals("mailto://www.example.com", cellHyperlink.getAddress()); + + // "mailto:" is converted to type "EMAIL" + assertEquals( + HyperlinkType.EMAIL, + cellHyperlink.getType()); + assertEquals( + isHSSF ? "url" : "mylabel", + cellHyperlink.getLabel()); + + workbook.write(out); + } + + out.flush(); + + try (Workbook wbBack = WorkbookFactory.create(new ByteArrayInputStream(out.toByteArray()))) { + Sheet sheet = wbBack.getSheet("Hyperlink Example"); + Row row = sheet.getRow(0); + Cell cell = row.getCell(0); + + Hyperlink hyperlink = cell.getHyperlink(); + + // "mailto:" is converted to type "EMAIL" + assertEquals( + HyperlinkType.EMAIL, + hyperlink.getType()); + assertEquals( + isHSSF ? "url" : "mylabel", + hyperlink.getLabel()); + } + } + } + public abstract Hyperlink copyHyperlink(Hyperlink link); } diff --git a/poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java b/poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java index cf6c2c7e9d..0d49bee3ab 100644 --- a/poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java +++ b/poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java @@ -1207,4 +1207,27 @@ class TestDataFormatter { assertEquals("25571.751069247686", df.formatCellValue(cell));*/ } } + + @Test + void testBug65190() { + DataFormatter formatter = new DataFormatter(Locale.ENGLISH); + + assertEquals("12334567890", + formatter.formatRawCellContents(12334567890.0, 0, "0")); + assertEquals("12334567890", + formatter.formatRawCellContents(12334567890.0, 0, "#")); + assertEquals("12334567890", + formatter.formatRawCellContents(12334567890.0, 0, "#0")); + assertEquals("12334567890", + formatter.formatRawCellContents(12334567890.0, 0, "0#")); + + assertEquals("12334567890123", + formatter.formatRawCellContents(12334567890123.0, 0, "0")); + assertEquals("12334567890123", + formatter.formatRawCellContents(12334567890123.0, 0, "#")); + assertEquals("12334567890123", + formatter.formatRawCellContents(12334567890123.0, 0, "#0")); + assertEquals("12334567890123", + formatter.formatRawCellContents(12334567890123.0, 0, "0#")); + } } diff --git a/poi/src/test/java/org/apache/poi/util/DefaultTempFileCreationStrategyTest.java b/poi/src/test/java/org/apache/poi/util/DefaultTempFileCreationStrategyTest.java new file mode 100644 index 0000000000..4e4f6b779e --- /dev/null +++ b/poi/src/test/java/org/apache/poi/util/DefaultTempFileCreationStrategyTest.java @@ -0,0 +1,149 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.util; + +import static org.apache.poi.util.DefaultTempFileCreationStrategy.DELETE_FILES_ON_EXIT; +import static org.apache.poi.util.DefaultTempFileCreationStrategy.POIFILES; +import static org.apache.poi.util.TempFile.JAVA_IO_TMPDIR; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; + +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class DefaultTempFileCreationStrategyTest { + + private String propBefore; + private String tmpBefore; + + @BeforeEach + void before() { + propBefore = System.getProperty(DELETE_FILES_ON_EXIT); + tmpBefore = System.getProperty(JAVA_IO_TMPDIR); + } + + @AfterEach + void after() { + if (propBefore == null) { + System.clearProperty(DELETE_FILES_ON_EXIT); + } else { + System.setProperty(DELETE_FILES_ON_EXIT, propBefore); + } + + System.setProperty(JAVA_IO_TMPDIR, tmpBefore); + } + + @Test + void testDefaultFile() throws IOException { + DefaultTempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(); + checkGetFile(strategy); + } + + private static void checkGetFile(DefaultTempFileCreationStrategy strategy) throws IOException { + File file = strategy.createTempFile("POITest", ".tmp"); + try { + assertTrue(file.getParentFile().exists(), + "Failed for " + file.getParentFile()); + + assertTrue(file.exists(), + "Failed for " + file); + } finally { + assertTrue(file.delete()); + } + } + + @Test + void testDefaultDir() throws IOException { + DefaultTempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(); + File dir = strategy.createTempDirectory("POITest"); + try { + assertTrue(dir.getParentFile().exists(), + "Failed for " + dir.getParentFile()); + + assertTrue(dir.exists(), + "Failed for " + dir); + } finally { + assertTrue(dir.delete()); + } + } + + @Test + void testWithProperty() throws IOException { + System.setProperty(DELETE_FILES_ON_EXIT, "true"); + + // we can set the property, but not easily check if it works + // so let's just call the main method + testDefaultFile(); + } + + @Test + void testEmptyTempDir() { + System.clearProperty(JAVA_IO_TMPDIR); + + DefaultTempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(); + assertThrows(IOException.class, + () -> strategy.createTempDirectory("POITest")); + } + + @Test + void testCustomDir() throws IOException { + File dirTest = File.createTempFile("POITest", ".dir"); + try { + assertTrue(dirTest.delete()); + + DefaultTempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(dirTest); + checkGetFile(strategy); + } finally { + FileUtils.deleteDirectory(dirTest); + } + } + + @Test + void testCustomDirExists() throws IOException { + File dirTest = File.createTempFile("POITest", ".dir"); + try { + assertTrue(dirTest.delete()); + assertTrue(dirTest.mkdir()); + + DefaultTempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(dirTest); + checkGetFile(strategy); + } finally { + FileUtils.deleteDirectory(dirTest); + } + } + + @Test + void testCustomDirAndPoiFilesExists() throws IOException { + File dirTest = File.createTempFile("POITest", ".dir"); + try { + assertTrue(dirTest.delete()); + assertTrue(dirTest.mkdir()); + assertTrue(new File(dirTest, POIFILES).mkdir()); + + DefaultTempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(dirTest); + checkGetFile(strategy); + } finally { + FileUtils.deleteDirectory(dirTest); + } + } +}
\ No newline at end of file diff --git a/test-data/diagram/clusterfuzz-testcase-minimized-POIVisioFuzzer-5842659694215168.vsdx b/test-data/diagram/clusterfuzz-testcase-minimized-POIVisioFuzzer-5842659694215168.vsdx Binary files differnew file mode 100644 index 0000000000..e6e6a3a902 --- /dev/null +++ b/test-data/diagram/clusterfuzz-testcase-minimized-POIVisioFuzzer-5842659694215168.vsdx diff --git a/test-data/document/clusterfuzz-testcase-minimized-POIHWPFFuzzer-5195207308541952.doc b/test-data/document/clusterfuzz-testcase-minimized-POIHWPFFuzzer-5195207308541952.doc Binary files differnew file mode 100644 index 0000000000..1d36046066 --- /dev/null +++ b/test-data/document/clusterfuzz-testcase-minimized-POIHWPFFuzzer-5195207308541952.doc diff --git a/test-data/document/header_footer_replace.doc b/test-data/document/header_footer_replace.doc Binary files differnew file mode 100644 index 0000000000..029ccb56f2 --- /dev/null +++ b/test-data/document/header_footer_replace.doc diff --git a/test-data/poifs/MailSentPropertyMultiple.msg b/test-data/poifs/MailSentPropertyMultiple.msg Binary files differnew file mode 100644 index 0000000000..d07feb178e --- /dev/null +++ b/test-data/poifs/MailSentPropertyMultiple.msg diff --git a/test-data/slideshow/clusterfuzz-testcase-minimized-POIXSLFFuzzer-4838644450394112.pptx b/test-data/slideshow/clusterfuzz-testcase-minimized-POIXSLFFuzzer-4838644450394112.pptx Binary files differnew file mode 100644 index 0000000000..b223ef94f8 --- /dev/null +++ b/test-data/slideshow/clusterfuzz-testcase-minimized-POIXSLFFuzzer-4838644450394112.pptx diff --git a/test-data/slideshow/clusterfuzz-testcase-minimized-POIXSLFFuzzer-4986044400861184.pptx b/test-data/slideshow/clusterfuzz-testcase-minimized-POIXSLFFuzzer-4986044400861184.pptx Binary files differnew file mode 100644 index 0000000000..df4a713ff0 --- /dev/null +++ b/test-data/slideshow/clusterfuzz-testcase-minimized-POIXSLFFuzzer-4986044400861184.pptx diff --git a/test-data/spreadsheet/stress.xls b/test-data/spreadsheet/stress.xls Binary files differindex 1667a30b74..ab8a975ae8 100644 --- a/test-data/spreadsheet/stress.xls +++ b/test-data/spreadsheet/stress.xls |