diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 27ec8027fb..766157603e 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 53282 - Avoid exception when parsing OPC relationships with non-breaking spaces 54016 - Avoid exception when parsing workbooks with DConRefRecord in row aggregate 54008 - Fixed Ant build to support build directories with blanks 53374 - Avoid exceptions when parsing hyperlinks of type "javascript://" diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackagingURIHelper.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackagingURIHelper.java index 37fdc5a58b..6385124b3f 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackagingURIHelper.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackagingURIHelper.java @@ -709,6 +709,25 @@ public final class PackagingURIHelper { value = path + "#" + encode(fragment); } + // trailing white spaces must be url-encoded, see Bugzilla 53282 + if(value.length() > 0 ){ + StringBuilder b = new StringBuilder(); + int idx = value.length() - 1; + for(; idx >= 0; idx--){ + char c = value.charAt(idx); + if(Character.isWhitespace(c) || c == '\u00A0') { + b.append(c); + } else { + break; + } + } + if(b.length() > 0){ + value = value.substring(0, idx+1) + encode(b.reverse().toString()); + } + } + + // MS Office can insert URIs with missing authority, e.g. "http://" or "javascript://" + // append a forward slash to avoid parse exception if(missingAuthPattern.matcher(value).matches()){ value += "/"; } @@ -756,7 +775,7 @@ public final class PackagingURIHelper { }; private static boolean isUnsafe(int ch) { - return ch > 0x80 || " ".indexOf(ch) >= 0; + return ch > 0x80 || Character.isWhitespace(ch) || ch == '\u00A0'; } } diff --git a/src/ooxml/testcases/org/apache/poi/openxml4j/opc/TestRelationships.java b/src/ooxml/testcases/org/apache/poi/openxml4j/opc/TestRelationships.java index 738175f54d..510377a9e0 100644 --- a/src/ooxml/testcases/org/apache/poi/openxml4j/opc/TestRelationships.java +++ b/src/ooxml/testcases/org/apache/poi/openxml4j/opc/TestRelationships.java @@ -19,6 +19,7 @@ package org.apache.poi.openxml4j.opc; import java.io.*; import java.net.URI; +import java.util.regex.Pattern; import junit.framework.TestCase; @@ -344,4 +345,32 @@ public class TestRelationships extends TestCase { assertEquals(rel1.getTargetURI(), rel2.getTargetURI()); assertEquals(rel1.getTargetMode(), rel2.getTargetMode()); } + + public void testTrailingSpacesInURI_53282() throws Exception { + InputStream is = OpenXML4JTestDataSamples.openSampleStream("53282.xlsx"); + OPCPackage pkg = OPCPackage.open(is); + is.close(); + + PackageRelationshipCollection sheetRels = pkg.getPartsByName(Pattern.compile("/xl/worksheets/sheet1.xml")).get(0).getRelationships(); + assertEquals(3, sheetRels.size()); + PackageRelationship rId1 = sheetRels.getRelationshipByID("rId1"); + assertEquals(TargetMode.EXTERNAL, rId1.getTargetMode()); + URI targetUri = rId1.getTargetURI(); + assertEquals("mailto:nobody@nowhere.uk%C2%A0", targetUri.toASCIIString()); + assertEquals("nobody@nowhere.uk\u00A0", targetUri.getSchemeSpecificPart()); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + pkg.save(out); + out.close(); + + pkg = OPCPackage.open(new ByteArrayInputStream(out.toByteArray())); + sheetRels = pkg.getPartsByName(Pattern.compile("/xl/worksheets/sheet1.xml")).get(0).getRelationships(); + assertEquals(3, sheetRels.size()); + rId1 = sheetRels.getRelationshipByID("rId1"); + assertEquals(TargetMode.EXTERNAL, rId1.getTargetMode()); + targetUri = rId1.getTargetURI(); + assertEquals("mailto:nobody@nowhere.uk%C2%A0", targetUri.toASCIIString()); + assertEquals("nobody@nowhere.uk\u00A0", targetUri.getSchemeSpecificPart()); + + } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFHyperlink.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFHyperlink.java index 9a79bf3cca..fbec25c670 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFHyperlink.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFHyperlink.java @@ -252,4 +252,14 @@ public final class TestXSSFHyperlink extends BaseTestHyperlink { link = wb.getSheetAt(0).getRow(0).getCell(0).getHyperlink(); assertEquals("javascript:///", link.getAddress()); } + + public void test53282() { + XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("53282.xlsx"); + XSSFHyperlink link = wb.getSheetAt(0).getRow(0).getCell(14).getHyperlink(); + assertEquals("mailto:nobody@nowhere.uk%C2%A0", link.getAddress()); + + wb = XSSFTestDataSamples.writeOutAndReadBack(wb); + link = wb.getSheetAt(0).getRow(0).getCell(14).getHyperlink(); + assertEquals("mailto:nobody@nowhere.uk%C2%A0", link.getAddress()); + } } diff --git a/test-data/openxml4j/53282.xlsx b/test-data/openxml4j/53282.xlsx new file mode 100644 index 0000000000..6d88303489 Binary files /dev/null and b/test-data/openxml4j/53282.xlsx differ diff --git a/test-data/spreadsheet/53282.xlsx b/test-data/spreadsheet/53282.xlsx new file mode 100644 index 0000000000..6d88303489 Binary files /dev/null and b/test-data/spreadsheet/53282.xlsx differ