Avoid several NPEs

When retrieving picture data
When retrieving text in slides
When handling XSLFTableStyles
In EmbeddedExtractor if ShapeName is not set
In HSSF with invalid EscherSpRecord
In HSSF with invalid RecordStreams
When drawing arcs for shapes in slides
In HSSFPicture.getPictureIndex
Adjust "opens" for tests in poi-ooxml

Either handle it gracefully or throw
IllegalStateException instead for broken files
This commit is contained in:
Dominik Stadler 2026-01-12 07:14:48 +01:00
parent d60edd21a3
commit 2acb5cf66a
18 changed files with 233 additions and 26 deletions

View File

@ -37,6 +37,8 @@ public abstract class SpreadsheetHandler extends AbstractFileHandler {
// try to access some of the content
readContent(wb);
extractEmbedded(wb);
// write out the file
writeToArray(wb);

View File

@ -259,7 +259,7 @@ public class POIXMLDocumentPart {
*/
public final POIXMLDocumentPart getRelationById(String id) {
RelationPart rp = getRelationPartById(id);
return (rp == null) ? null : rp.getDocumentPart();
return rp == null ? null : rp.getDocumentPart();
}
/**
@ -797,7 +797,7 @@ public class POIXMLDocumentPart {
* @since 5.3.0
*/
public final HyperlinkRelationship createHyperlink(URI uri, boolean isExternal, String relId) {
PackageRelationship pr = packagePart.addRelationship(uri, isExternal ? TargetMode.EXTERNAL : TargetMode.INTERNAL,
packagePart.addRelationship(uri, isExternal ? TargetMode.EXTERNAL : TargetMode.INTERNAL,
PackageRelationshipTypes.HYPERLINK_PART, relId);
HyperlinkRelationship hyperlink = new HyperlinkRelationship(this, uri, isExternal, relId);
referenceRelationships.put(relId, hyperlink);

View File

@ -340,6 +340,9 @@ public class XSLFTable extends XSLFGraphicFrame implements Iterable<XSLFTableRow
String styleId = tab.getTblPr().getTableStyleId();
XSLFTableStyles styles = getSheet().getSlideShow().getTableStyles();
if (styles == null) {
return null;
}
for (XSLFTableStyle style : styles.getStyles()) {
if (style.getStyleId().equals(styleId)) {
return style;

View File

@ -263,10 +263,15 @@ public final class XSSFPicture extends XSSFShape implements Picture {
/**
* Return picture data for this shape
*
* @return picture data for this shape
* @return picture data for this shape or null if
* the data cannot be retrieved
*/
@Override
public XSSFPictureData getPictureData() {
if (ctPicture.getBlipFill().getBlip() == null) {
return null;
}
String blipId = ctPicture.getBlipFill().getBlip().getEmbed();
return (XSSFPictureData)getDrawing().getRelationById(blipId);
}

View File

@ -0,0 +1,62 @@
/* ====================================================================
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.xslf.draw.geom;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.awt.geom.Path2D;
import org.apache.poi.sl.draw.geom.ArcToCommand;
import org.apache.poi.sl.draw.geom.Context;
import org.apache.poi.sl.draw.geom.CustomGeometry;
import org.junit.jupiter.api.Test;
class TestXSLFArcTo {
@Test
void test() {
ArcToCommand arc = new ArcToCommand();
CustomGeometry geom = new CustomGeometry();
Context ctx = new Context(geom, null, null) {
@Override
public double getValue(String key) {
return 1.0;
}
};
Path2D.Double path = new Path2D.Double();
path.moveTo(1.0, 1.0);
path.lineTo(2.0, 2.0);
arc.execute(path, ctx);
}
@Test
void testPointFails() {
ArcToCommand arc = new ArcToCommand();
CustomGeometry geom = new CustomGeometry();
Context ctx = new Context(geom, null, null) {
@Override
public double getValue(String key) {
return 1.0;
}
};
assertThrows(IllegalStateException.class,
() -> arc.execute(new Path2D.Double(), ctx));
}
}

View File

@ -19,6 +19,7 @@ package org.apache.poi.xslf.usermodel;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
@ -80,6 +81,7 @@ class TestXSLFTable {
tab.removeColumn(0);
tab.removeColumn(tab.getNumberOfColumns() - 1);
assertEquals(data[0].length, tab.getNumberOfColumns());
assertNull(tab.getTableStyle());
int startRow = rowIdx-1;
@ -163,7 +165,7 @@ class TestXSLFTable {
XSLFSlide slide = ppt.getSlides().get(3);
List<XSLFShape> shapes = slide.getShapes();
assertEquals(1, shapes.size());
assertTrue(shapes.get(0) instanceof XSLFTable);
assertInstanceOf(XSLFTable.class, shapes.get(0));
XSLFTable tbl = (XSLFTable)shapes.get(0);
assertEquals(3, tbl.getNumberOfColumns());
assertEquals(6, tbl.getNumberOfRows());
@ -175,6 +177,7 @@ class TestXSLFTable {
assertEquals(90.0, tbl.getColumnWidth(0), 0);
assertEquals(240.0, tbl.getColumnWidth(1), 0);
assertEquals(150.0, tbl.getColumnWidth(2), 0);
assertNotNull(tbl.getTableStyle());
for(XSLFTableRow row : tbl){
// all rows have the same height
@ -207,7 +210,7 @@ class TestXSLFTable {
assertNotNull(tbl.getCTTable());
assertNotNull(tbl.getCTTable().getTblGrid());
assertNotNull(tbl.getCTTable().getTblPr());
assertTrue(tbl.getXmlObject() instanceof CTGraphicalObjectFrame);
assertInstanceOf(CTGraphicalObjectFrame.class, tbl.getXmlObject());
assertEquals("Table 2", tbl.getShapeName());
assertEquals(2, tbl.getShapeId());
assertEquals(0, tbl.getRows().size());
@ -216,6 +219,7 @@ class TestXSLFTable {
assertEquals(0, tbl.getNumberOfColumns());
assertEquals(0, tbl.getNumberOfRows());
assertNull(tbl.getTableStyle());
XSLFTableRow row0 = tbl.addRow();
assertNotNull(row0.getXmlObject());
@ -281,6 +285,7 @@ class TestXSLFTable {
XMLSlideShow ss = XSLFTestDataSamples.openSampleDocument("shapes.pptx");
XSLFSlide sl = ss.getSlides().get(0);
XSLFTable tab = (XSLFTable)sl.getShapes().get(4);
assertNotNull(tab.getTableStyle());
sl.removeShape(tab);
XMLSlideShow ss2 = XSLFTestDataSamples.writeOutAndReadBack(ss);
@ -305,6 +310,7 @@ class TestXSLFTable {
XSLFTableCell tc0 = tr.addCell();
tc0.setText("bla bla bla bla");
tab.setColumnWidth(0, 50);
assertNull(tab.getTableStyle());
// usually text height == 88, but font rendering is platform dependent
// so we use something more reliable
@ -355,13 +361,14 @@ class TestXSLFTable {
new DrawTableShape(newTable).setAllBorders(3., StrokeStyle.LineDash.LG_DASH_DOT, Color.BLUE);
assertEquals(3, newTable.getCTTable().getTblGrid().sizeOfGridColArray());
assertNull(newTable.getTableStyle());
}
}
private void verifyTableCellStyleColors(XSLFTableCell cell, String text, Color fontColor, Color fillColor) {
assertEquals(text, cell.getText());
PaintStyle colorText1 = cell.getTextParagraphs().get(0).getTextRuns().get(0).getFontColor();
assertTrue(colorText1 instanceof PaintStyle.SolidPaint);
assertInstanceOf(PaintStyle.SolidPaint.class, colorText1);
assertEquals(fontColor, ((PaintStyle.SolidPaint)colorText1).getSolidColor().getColor());
assertEquals(fillColor, cell.getFillColor());
}
@ -374,11 +381,12 @@ class TestXSLFTable {
List<XSLFShape> shapes = ppt.getSlides().get(0).getShapes();
assertEquals(1, shapes.size());
assertTrue(shapes.get(0) instanceof XSLFTable);
assertInstanceOf(XSLFTable.class, shapes.get(0));
XSLFTable tbl = (XSLFTable)shapes.get(0);
assertEquals(4, tbl.getNumberOfColumns());
assertEquals(4, tbl.getNumberOfRows());
assertNotNull(tbl.getCTTable());
assertNotNull(tbl.getTableStyle());
// Yellow font color due to "first row" table style
verifyTableCellStyleColors(tbl.getRows().get(0).getCells().get(0), "Text 1",
@ -403,11 +411,12 @@ class TestXSLFTable {
shapes = ppt.getSlides().get(1).getShapes();
assertEquals(1, shapes.size());
assertTrue(shapes.get(0) instanceof XSLFTable);
assertInstanceOf(XSLFTable.class, shapes.get(0));
tbl = (XSLFTable)shapes.get(0);
assertEquals(4, tbl.getNumberOfColumns());
assertEquals(4, tbl.getNumberOfRows());
assertNotNull(tbl.getCTTable());
assertNotNull(tbl.getTableStyle());
// Green font color due to "first column" table style
verifyTableCellStyleColors(tbl.getRows().get(0).getCells().get(0), "Text 1",

View File

@ -200,6 +200,7 @@ module org.apache.poi.ooxml {
opens org.apache.poi.xssf.streaming to org.junit.platform.commons;
opens org.apache.poi.xssf.util to org.junit.platform.commons;
opens org.apache.poi.xslf.draw to org.junit.platform.commons;
opens org.apache.poi.xslf.draw.geom to org.junit.platform.commons;
opens org.apache.poi.xslf.usermodel to org.junit.platform.commons;
opens org.apache.poi.xslf.model to org.junit.platform.commons;
opens org.apache.poi.xslf.util to org.junit.platform.commons;

View File

@ -1075,6 +1075,10 @@ public final class InternalSheet {
}
private void setColumn(int column, Short xfStyle, Integer width, Integer level, Boolean hidden, Boolean collapsed) {
if (_columnInfos == null) {
throw new IllegalStateException("Cannot group column range with missing column-infos");
}
_columnInfos.setColumn( column, xfStyle, width, level, hidden, collapsed );
}
@ -1087,6 +1091,9 @@ public final class InternalSheet {
* if false indenting will be removed by one level.
*/
public void groupColumnRange(int fromColumn, int toColumn, boolean indent) {
if (_columnInfos == null) {
throw new IllegalStateException("Cannot group column range with missing column-infos");
}
// Set the level for each column
_columnInfos.groupColumnRange( fromColumn, toColumn, indent);

View File

@ -52,25 +52,27 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture {
/**
* Constructs a picture object.
*/
public HSSFPicture( HSSFShape parent, HSSFAnchor anchor )
{
public HSSFPicture( HSSFShape parent, HSSFAnchor anchor ) {
super( parent, anchor );
super.setShapeType(OBJECT_TYPE_PICTURE);
CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) getObjRecord().getSubRecords().get(0);
cod.setObjectType(CommonObjectDataSubRecord.OBJECT_TYPE_PICTURE);
}
public int getPictureIndex()
{
EscherSimpleProperty property = getOptRecord().lookup(EscherPropertyTypes.BLIP__BLIPTODISPLAY);
if (null == property){
public int getPictureIndex() {
EscherOptRecord optRecord = getOptRecord();
if (optRecord == null) {
return -1;
}
EscherSimpleProperty property = optRecord.lookup(EscherPropertyTypes.BLIP__BLIPTODISPLAY);
if (null == property) {
return -1;
}
return property.getPropertyValue();
}
public void setPictureIndex( int pictureIndex )
{
public void setPictureIndex( int pictureIndex ) {
setPropertyValue(new EscherSimpleProperty( EscherPropertyTypes.BLIP__BLIPTODISPLAY, false, true, pictureIndex));
}
@ -95,7 +97,7 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture {
* </p>
*/
@Override
public void resize(){
public void resize() {
resize(Double.MAX_VALUE);
}
@ -152,7 +154,7 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture {
* @since 3.0.2
*/
@Override
public HSSFClientAnchor getPreferredSize(){
public HSSFClientAnchor getPreferredSize() {
return getPreferredSize(1.0);
}
@ -163,7 +165,7 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture {
* @return HSSFClientAnchor with the preferred size for this image
* @since 3.0.2
*/
public HSSFClientAnchor getPreferredSize(double scale){
public HSSFClientAnchor getPreferredSize(double scale) {
return getPreferredSize(scale, scale);
}
@ -176,7 +178,7 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture {
* @since 3.11
*/
@Override
public HSSFClientAnchor getPreferredSize(double scaleX, double scaleY){
public HSSFClientAnchor getPreferredSize(double scaleX, double scaleY) {
ImageUtils.setPreferredSize(this, scaleX, scaleY);
return getClientAnchor();
}
@ -187,7 +189,7 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture {
* @return image dimension in pixels
*/
@Override
public Dimension getImageDimension(){
public Dimension getImageDimension() {
InternalWorkbook iwb = getPatriarch().getSheet().getWorkbook().getWorkbook();
EscherBSERecord bse = iwb.getBSERecord(getPictureIndex());
byte[] data = bse.getBlipRecord().getPicturedata();
@ -206,7 +208,7 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture {
* @return picture data for this shape or {@code null} if picture wasn't embedded, i.e. external linked
*/
@Override
public HSSFPictureData getPictureData(){
public HSSFPictureData getPictureData() {
int picIdx = getPictureIndex();
if (picIdx == -1) {
return null;
@ -249,7 +251,7 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture {
: StringUtil.getFromUnicodeLE(propFile.getComplexData()).trim();
}
public void setFileName(String data){
public void setFileName(String data) {
// TODO: add trailing \u0000?
byte[] bytes = StringUtil.getToUnicodeLE(data);
EscherComplexProperty prop = new EscherComplexProperty(EscherPropertyTypes.BLIP__BLIPFILENAME, true, bytes.length);

View File

@ -115,6 +115,10 @@ public abstract class HSSFShape implements Shape {
*/
void setShapeId(int shapeId){
EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID);
if (spRecord == null) {
throw new IllegalStateException("Did not have an EscherSpRecord, cannot set shape id " + shapeId);
}
spRecord.setShapeId(shapeId);
CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) _objRecord.getSubRecords().get(0);
cod.setObjectId((short) (shapeId%1024));

View File

@ -591,10 +591,10 @@ public class DrawTextParagraph implements Drawable {
final Map<Attribute,Object> att = new HashMap<>();
final List<AttributedStringData> attList = new ArrayList<>();
for (TextRun run : paragraph){
for (TextRun run : paragraph) {
String runText = getRenderableText(graphics, run);
// skip empty runs
if (runText.isEmpty()) {
if (runText == null || runText.isEmpty()) {
continue;
}

View File

@ -60,6 +60,10 @@ public interface ArcToCommandIf extends PathCommand {
double invStart = Math.atan2(rx * Math.sin(radStart), ry * Math.cos(radStart));
Point2D pt = path.getCurrentPoint();
if (pt == null) {
throw new IllegalStateException("Cannot draw arc without valid point");
}
// calculate top/left corner
double x0 = pt.getX() - rx * Math.cos(invStart) - rx;
double y0 = pt.getY() - ry * Math.sin(invStart) - ry;

View File

@ -260,7 +260,7 @@ public class EmbeddedExtractor implements Iterable<EmbeddedExtractor> {
int pictureBytesLen = idxEnd-idxStart+6;
byte[] pdfBytes = IOUtils.safelyClone(pictureBytes, idxStart, pictureBytesLen, MAX_RECORD_LENGTH);
String filename = source.getShapeName().trim();
String filename = source.getShapeName() == null ? "empty" : source.getShapeName().trim();
if (!endsWithIgnoreCase(filename, ".pdf")) {
filename += ".pdf";
}

View File

@ -0,0 +1,72 @@
/* ====================================================================
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.hssf.model;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.List;
import org.apache.poi.hssf.record.BOFRecord;
import org.apache.poi.hssf.record.EOFRecord;
import org.apache.poi.hssf.record.WindowTwoRecord;
import org.apache.poi.util.RecordFormatException;
import org.junit.jupiter.api.Test;
class TestInternalSheet {
@Test
void testEmptySheet() {
InternalSheet sheet = InternalSheet.createSheet();
sheet.groupColumnRange(0, 0, true);
sheet.groupRowRange(0, 0, true);
sheet.setDefaultColumnStyle(0, 0);
}
@Test
void testMissingBOFRecord() {
assertThrows(RecordFormatException.class,
() -> InternalSheet.createSheet(new RecordStream(
List.of(new BOFRecord()), 0)));
}
@Test
void testInvalidBOFRecord() {
assertThrows(RecordFormatException.class,
() -> InternalSheet.createSheet(new RecordStream(
List.of(new BOFRecord()), 0)));
}
@Test
void testInvalidBOFRecord2() {
assertThrows(RecordFormatException.class,
() -> InternalSheet.createSheet(new RecordStream(
List.of(BOFRecord.createSheetBOF()), 0)));
}
@Test
void testEmptyRecordStream() {
InternalSheet sheet = InternalSheet.createSheet(new RecordStream(
List.of(BOFRecord.createSheetBOF(),
new WindowTwoRecord(),
EOFRecord.instance), 0));
assertThrows(IllegalStateException.class,
() -> sheet.groupColumnRange(0, 0, true));
sheet.groupRowRange(0, 0, true);
assertThrows(IllegalStateException.class,
() -> sheet.setDefaultColumnStyle(0, 0));
}
}

View File

@ -22,10 +22,12 @@ 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.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherSpRecord;
import org.apache.poi.hssf.HSSFITestDataProvider;
import org.apache.poi.hssf.HSSFTestDataSamples;
@ -362,6 +364,7 @@ final class TestHSSFComment extends BaseTestCellComment {
void existingFileWithComment() throws IOException {
try (HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls")) {
HSSFSheet sheet = wb.getSheet("comments");
assertNotNull(sheet);
HSSFPatriarch drawing = sheet.getDrawingPatriarch();
assertEquals(1, drawing.getChildren().size());
HSSFComment comment = (HSSFComment) drawing.getChildren().get(0);
@ -434,4 +437,11 @@ final class TestHSSFComment extends BaseTestCellComment {
assertEquals(2024, comment.getNoteRecord().getShapeId());
}
}
@Test
void getEmptyShape() throws IOException {
HSSFComment shape = new HSSFComment(new EscherContainerRecord(), null, null, null);
assertThrows(IllegalStateException.class,
() -> shape.setShapeId(1));
}
}

View File

@ -21,6 +21,7 @@ import static org.apache.poi.hssf.HSSFTestDataSamples.openSampleWorkbook;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.io.IOException;
import java.util.Arrays;
@ -28,6 +29,10 @@ import java.util.List;
import org.apache.poi.POIDataSamples;
import org.apache.poi.ddf.EscherBSERecord;
import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherOptRecord;
import org.apache.poi.ddf.EscherPropertyTypes;
import org.apache.poi.ddf.EscherTextboxRecord;
import org.apache.poi.hssf.HSSFITestDataProvider;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.model.InternalSheet;
@ -285,4 +290,25 @@ final class TestHSSFPicture extends BaseTestPicture {
}
}
@Test
void testEmptyOptRecord() throws IOException {
try (HSSFWorkbook wb = new HSSFWorkbook()) {
HSSFSheet sh = wb.createSheet("Pictures");
HSSFPatriarch dr = sh.createDrawingPatriarch();
HSSFShapeGroup gr = dr.createGroup(new HSSFClientAnchor());
HSSFPicture pic = new HSSFPicture(gr, new HSSFClientAnchor()) {
@Override
protected EscherContainerRecord createSpContainer() {
EscherContainerRecord spContainer = super.createSpContainer();
EscherOptRecord opt = spContainer.getChildById(EscherOptRecord.RECORD_ID);
spContainer.removeChildRecord(opt);
spContainer.removeChildRecord(spContainer.getChildById(EscherTextboxRecord.RECORD_ID));
return spContainer;
}
};
assertNull(pic.getOptRecord());
assertEquals(-1, pic.getPictureIndex());
}
}
}

Binary file not shown.