diff options
| -rw-r--r-- | NEWS | 4 | ||||
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java | 74 | ||||
| -rw-r--r-- | src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java | 52 | ||||
| -rw-r--r-- | src/test/java/org/zwobble/mammoth/tests/docx/DocumentMatchers.java | 5 |
5 files changed, 119 insertions, 17 deletions
@@ -1,3 +1,7 @@ +# 1.12.0 + +* Handle hyperlinked wp:anchor and wp:inline elements. + # 1.11.0 * Ignore style definitions using a style ID that has already been used. @@ -330,6 +330,7 @@ For instance: automatically convert the document into HTML on the server, and embed the HTML into your website, this may allow arbitrary files on the server to be read and exfiltrated. + To avoid this issue, access to any such external files is disabled by default. To enable access when converting trusted source documents, call `enableExternalFileAccess()`. diff --git a/src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java b/src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java index 1f5aff4..9983d09 100644 --- a/src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java +++ b/src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java @@ -694,34 +694,52 @@ class StatefulBodyXmlReader { private ReadResult readInline(XmlElement element) { XmlElementLike properties = element.findChildOrEmpty("wp:docPr"); + Optional<String> altText = Optionals.first( properties.getAttributeOrNone("descr").filter(description -> !description.trim().isEmpty()), properties.getAttributeOrNone("title") ); + + XmlElementLike hlinkClickElement = properties.findChildOrEmpty("a:hlinkClick"); + Optional<String> href = hlinkClickElement.getAttributeOrNone("r:id") + .map(relationships::findTargetByRelationshipId); + XmlElementList blips = element.findChildren("a:graphic") .findChildren("a:graphicData") .findChildren("pic:pic") .findChildren("pic:blipFill") .findChildren("a:blip"); - return readBlips(blips, altText); + return readBlips(blips, altText, href); } - private ReadResult readBlips(XmlElementList blips, Optional<String> altText) { - return ReadResult.flatMap(blips, blip -> readBlip(blip, altText)); + private ReadResult readBlips( + XmlElementList blips, + Optional<String> altText, + Optional<String> href + ) { + return ReadResult.flatMap(blips, blip -> readBlip(blip, altText, href)); } - private ReadResult readBlip(XmlElement blip, Optional<String> altText) { - Optional<String> embedRelationshipId = blip.getAttributeOrNone("r:embed"); - Optional<String> linkRelationshipId = blip.getAttributeOrNone("r:link"); - if (embedRelationshipId.isPresent()) { - String imagePath = relationshipIdToDocxPath(embedRelationshipId.get()); - return readImage(imagePath, altText, () -> Archives.getInputStream(file, imagePath)); - } else if (linkRelationshipId.isPresent()) { - String imagePath = relationships.findTargetByRelationshipId(linkRelationshipId.get()); - return readImage(imagePath, altText, () -> fileReader.getInputStream(imagePath)); - } else { + private ReadResult readBlip( + XmlElement blip, + Optional<String> altText, + Optional<String> href + ) { + Optional<BlipImage> blipImage = findBlipImage(blip); + + if (!blipImage.isPresent()) { return ReadResult.emptyWithWarning("Could not find image file for a:blip element"); } + + ReadResult result = readImage(blipImage.get().path, altText, blipImage.get().open); + + if (!href.isPresent()) { + return result; + } + + return result.map(imageElements -> list( + new Hyperlink(href, Optional.empty(), Optional.empty(), imageElements) + )); } private ReadResult readImage(String imagePath, Optional<String> altText, InputStreamSupplier open) { @@ -736,6 +754,36 @@ class StatefulBodyXmlReader { } } + private static class BlipImage { + private final String path; + private final InputStreamSupplier open; + + public BlipImage(String path, InputStreamSupplier open) { + this.path = path; + this.open = open; + } + } + + private Optional<BlipImage> findBlipImage(XmlElement blip) { + Optional<String> embedRelationshipId = blip.getAttributeOrNone("r:embed"); + Optional<String> linkRelationshipId = blip.getAttributeOrNone("r:link"); + if (embedRelationshipId.isPresent()) { + String imagePath = relationshipIdToDocxPath(embedRelationshipId.get()); + return Optional.of(new BlipImage( + imagePath, + () -> Archives.getInputStream(file, imagePath) + )); + } else if (linkRelationshipId.isPresent()) { + String imagePath = relationships.findTargetByRelationshipId(linkRelationshipId.get()); + return Optional.of(new BlipImage( + imagePath, + () -> fileReader.getInputStream(imagePath) + )); + } else { + return Optional.empty(); + } + } + private ReadResult readSdt(XmlElement element) { ReadResult contentResult = readElements( element.findChildOrEmpty("w:sdtContent").getChildren() diff --git a/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java b/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java index ea3e3f3..71d8627 100644 --- a/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java +++ b/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java @@ -1944,6 +1944,42 @@ public class BodyXmlTests { assertThat(result, isInternalSuccess(empty())); } + @Test + public void canReadPicturesWithHyperlinkSpecifiedInDocumentProperties() throws IOException { + XmlElement element = element("w:drawing", list( + element("wp:inline", list( + element("wp:docPr", map(), list( + element("a:hlinkClick", map("r:id", "rId42")) + )), + graphicXml(embeddedBlipXml(IMAGE_RELATIONSHIP_ID)) + )) + )); + Relationships relationships = new Relationships(list( + imageRelationship(IMAGE_RELATIONSHIP_ID, "media/hat.png"), + hyperlinkRelationship("rId42", "http://example.com") + )); + Archive file = InMemoryArchive.fromStrings(map("word/media/hat.png", IMAGE_BYTES)); + + DocumentElement result = readSuccess( + bodyReader(relationships, file), + element + ); + + assertThat(result, isHyperlink( + hasHref("http://example.com"), + hasChildren( + isImage( + hasProperty("contentType", deepEquals(Optional.of("image/png"))) + ) + ) + )); + Image image = (Image) ((Hyperlink) result).getChildren().get(0); + assertThat( + toString(image.open()), + equalTo(IMAGE_BYTES) + ); + } + private XmlElement inlineImageXml(XmlElement blip, String description) { return inlineImageXml(blip, Optional.of(description), Optional.empty()); } @@ -1965,10 +2001,18 @@ public class BodyXmlTests { return list( element("wp:docPr", properties), - element("a:graphic", list( - element("a:graphicData", list( - element("pic:pic", list( - element("pic:blipFill", list(blip))))))))); + graphicXml(blip) + ); + } + + private XmlNode graphicXml(XmlElement blip) { + return element("a:graphic", list( + element("a:graphicData", list( + element("pic:pic", list( + element("pic:blipFill", list(blip)) + )) + )) + )); } private XmlElement embeddedBlipXml(String relationshipId) { diff --git a/src/test/java/org/zwobble/mammoth/tests/docx/DocumentMatchers.java b/src/test/java/org/zwobble/mammoth/tests/docx/DocumentMatchers.java index 67e15f0..47c5d03 100644 --- a/src/test/java/org/zwobble/mammoth/tests/docx/DocumentMatchers.java +++ b/src/test/java/org/zwobble/mammoth/tests/docx/DocumentMatchers.java @@ -122,4 +122,9 @@ public class DocumentMatchers { static Matcher<Hyperlink> hasNoTargetFrame() { return hasProperty("targetFrame", equalTo(Optional.empty())); } + + @SafeVarargs + static Matcher<DocumentElement> isImage(Matcher<? super Image>... matchers) { + return cast(Image.class, allOf(matchers)); + } } |
