aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/zwobble/mammoth/DocumentConverter.java9
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/InternalDocumentConverter.java4
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtmlOptions.java88
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/docx/DisabledFileReader.java11
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/docx/DocumentReader.java12
-rw-r--r--src/test/java/org/zwobble/mammoth/tests/MammothTests.java23
-rw-r--r--src/test/java/org/zwobble/mammoth/tests/docx/DocumentReaderTests.java55
7 files changed, 173 insertions, 29 deletions
diff --git a/src/main/java/org/zwobble/mammoth/DocumentConverter.java b/src/main/java/org/zwobble/mammoth/DocumentConverter.java
index 2dea8e8..c7dca20 100644
--- a/src/main/java/org/zwobble/mammoth/DocumentConverter.java
+++ b/src/main/java/org/zwobble/mammoth/DocumentConverter.java
@@ -61,6 +61,15 @@ public class DocumentConverter {
}
/**
+ * Source documents may reference files outside of the source document.
+ * Call this to disable access to any such external files during the conversion process.
+ * This is highly recommended when converting untrusted user input.
+ */
+ public DocumentConverter disableExternalFileAccess() {
+ return new DocumentConverter(options.disableExternalFileAccess());
+ }
+
+ /**
* By default, images are converted to {@code <img>} elements with the source included inline in the {@code src} attribute.
* Call this to change how images are converted.
*/
diff --git a/src/main/java/org/zwobble/mammoth/internal/InternalDocumentConverter.java b/src/main/java/org/zwobble/mammoth/internal/InternalDocumentConverter.java
index 8ff1e95..3e01181 100644
--- a/src/main/java/org/zwobble/mammoth/internal/InternalDocumentConverter.java
+++ b/src/main/java/org/zwobble/mammoth/internal/InternalDocumentConverter.java
@@ -45,7 +45,7 @@ public class InternalDocumentConverter {
Optional<StyleMap> styleMap = readEmbeddedStyleMap(zipFile).map(StyleMapParser::parse);
DocumentToHtmlOptions conversionOptions = styleMap.map(options::addEmbeddedStyleMap).orElse(options);
- return readDocument(path, zipFile)
+ return readDocument(path, zipFile, conversionOptions.externalFileAccess())
.flatMap(nodes -> DocumentToHtml.convertToHtml(nodes, conversionOptions))
.map(Html::stripEmpty)
.map(Html::collapse)
@@ -69,7 +69,7 @@ public class InternalDocumentConverter {
}
private InternalResult<String> extractRawText(Optional<Path> path, Archive zipFile) {
- return readDocument(path, zipFile)
+ return readDocument(path, zipFile, options.externalFileAccess())
.map(RawText::extractRawText);
}
diff --git a/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtmlOptions.java b/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtmlOptions.java
index 2c455e2..d12bbd4 100644
--- a/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtmlOptions.java
+++ b/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtmlOptions.java
@@ -16,6 +16,7 @@ public class DocumentToHtmlOptions {
StyleMap.EMPTY,
false,
false,
+ false,
InternalImageConverter.imgElement(image -> {
String base64 = Base64Encoding.streamToBase64(image::getInputStream);
String src = "data:" + image.getContentType() + ";base64," + base64;
@@ -30,6 +31,7 @@ public class DocumentToHtmlOptions {
private final StyleMap embeddedStyleMap;
private final boolean disableDefaultStyleMap;
private final boolean disableEmbeddedStyleMap;
+ private final boolean disableExternalFileAccess;
private final InternalImageConverter imageConverter;
public DocumentToHtmlOptions(
@@ -39,6 +41,7 @@ public class DocumentToHtmlOptions {
StyleMap embeddedStyleMap,
boolean disableDefaultStyleMap,
boolean disableEmbeddedStyleMap,
+ boolean disableExternalFileAccess,
InternalImageConverter imageConverter
) {
this.idPrefix = idPrefix;
@@ -47,15 +50,34 @@ public class DocumentToHtmlOptions {
this.embeddedStyleMap = embeddedStyleMap;
this.disableDefaultStyleMap = disableDefaultStyleMap;
this.disableEmbeddedStyleMap = disableEmbeddedStyleMap;
+ this.disableExternalFileAccess = disableExternalFileAccess;
this.imageConverter = imageConverter;
}
public DocumentToHtmlOptions idPrefix(String prefix) {
- return new DocumentToHtmlOptions(prefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap, imageConverter);
+ return new DocumentToHtmlOptions(
+ prefix,
+ preserveEmptyParagraphs,
+ styleMap,
+ embeddedStyleMap,
+ disableDefaultStyleMap,
+ disableEmbeddedStyleMap,
+ disableExternalFileAccess,
+ imageConverter
+ );
}
public DocumentToHtmlOptions preserveEmptyParagraphs() {
- return new DocumentToHtmlOptions(idPrefix, true, styleMap, embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap, imageConverter);
+ return new DocumentToHtmlOptions(
+ idPrefix,
+ true,
+ styleMap,
+ embeddedStyleMap,
+ disableDefaultStyleMap,
+ disableEmbeddedStyleMap,
+ disableExternalFileAccess,
+ imageConverter
+ );
}
public DocumentToHtmlOptions addStyleMap(String styleMap) {
@@ -63,19 +85,68 @@ public class DocumentToHtmlOptions {
}
public DocumentToHtmlOptions addStyleMap(StyleMap styleMap) {
- return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, this.styleMap.update(styleMap), embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap, imageConverter);
+ return new DocumentToHtmlOptions(
+ idPrefix,
+ preserveEmptyParagraphs,
+ this.styleMap.update(styleMap),
+ embeddedStyleMap,
+ disableDefaultStyleMap,
+ disableEmbeddedStyleMap,
+ disableExternalFileAccess,
+ imageConverter
+ );
}
public DocumentToHtmlOptions disableDefaultStyleMap() {
- return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, true, disableEmbeddedStyleMap, imageConverter);
+ return new DocumentToHtmlOptions(
+ idPrefix,
+ preserveEmptyParagraphs,
+ styleMap,
+ embeddedStyleMap,
+ true,
+ disableEmbeddedStyleMap,
+ disableExternalFileAccess,
+ imageConverter
+ );
}
public DocumentToHtmlOptions disableEmbeddedStyleMap() {
- return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, disableDefaultStyleMap, true, imageConverter);
+ return new DocumentToHtmlOptions(
+ idPrefix,
+ preserveEmptyParagraphs,
+ styleMap,
+ embeddedStyleMap,
+ disableDefaultStyleMap,
+ true,
+ disableExternalFileAccess,
+ imageConverter
+ );
+ }
+
+ public DocumentToHtmlOptions disableExternalFileAccess() {
+ return new DocumentToHtmlOptions(
+ idPrefix,
+ preserveEmptyParagraphs,
+ styleMap,
+ embeddedStyleMap,
+ disableDefaultStyleMap,
+ disableEmbeddedStyleMap,
+ true,
+ imageConverter
+ );
}
public DocumentToHtmlOptions addEmbeddedStyleMap(StyleMap embeddedStyleMap) {
- return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap, imageConverter);
+ return new DocumentToHtmlOptions(
+ idPrefix,
+ preserveEmptyParagraphs,
+ styleMap,
+ embeddedStyleMap,
+ disableDefaultStyleMap,
+ disableEmbeddedStyleMap,
+ disableExternalFileAccess,
+ imageConverter
+ );
}
public DocumentToHtmlOptions imageConverter(ImageConverter.ImgElement imageConverter) {
@@ -86,6 +157,7 @@ public class DocumentToHtmlOptions {
embeddedStyleMap,
disableDefaultStyleMap,
disableEmbeddedStyleMap,
+ disableExternalFileAccess,
InternalImageConverter.imgElement(imageConverter)
);
}
@@ -110,6 +182,10 @@ public class DocumentToHtmlOptions {
return styleMap;
}
+ public boolean externalFileAccess() {
+ return !this.disableExternalFileAccess;
+ }
+
public InternalImageConverter imageConverter() {
return imageConverter;
}
diff --git a/src/main/java/org/zwobble/mammoth/internal/docx/DisabledFileReader.java b/src/main/java/org/zwobble/mammoth/internal/docx/DisabledFileReader.java
new file mode 100644
index 0000000..03386d4
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/docx/DisabledFileReader.java
@@ -0,0 +1,11 @@
+package org.zwobble.mammoth.internal.docx;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class DisabledFileReader implements FileReader {
+ @Override
+ public InputStream getInputStream(String uri) throws IOException {
+ throw new IOException("could not open external image '" + uri + "': external file access is disabled");
+ }
+}
diff --git a/src/main/java/org/zwobble/mammoth/internal/docx/DocumentReader.java b/src/main/java/org/zwobble/mammoth/internal/docx/DocumentReader.java
index 1955e55..495646f 100644
--- a/src/main/java/org/zwobble/mammoth/internal/docx/DocumentReader.java
+++ b/src/main/java/org/zwobble/mammoth/internal/docx/DocumentReader.java
@@ -22,13 +22,21 @@ import static org.zwobble.mammoth.internal.util.Lists.*;
import static org.zwobble.mammoth.internal.util.Strings.trimLeft;
public class DocumentReader {
- public static InternalResult<Document> readDocument(Optional<Path> path, Archive zipFile) {
+ public static InternalResult<Document> readDocument(
+ Optional<Path> path,
+ Archive zipFile,
+ boolean externalFileAccess
+ ) {
PartPaths partPaths = findPartPaths(zipFile);
Styles styles = readStyles(zipFile, partPaths);
Numbering numbering = readNumbering(zipFile, partPaths, styles);
ContentTypes contentTypes = readContentTypes(zipFile);
- FileReader fileReader = new PathRelativeFileReader(path);
+
+ FileReader fileReader = externalFileAccess
+ ? new PathRelativeFileReader(path)
+ : new DisabledFileReader();
+
PartWithBodyReader partReader = new PartWithBodyReader(zipFile, contentTypes, fileReader, numbering, styles);
return InternalResult.flatMap(
readNotes(partReader, partPaths),
diff --git a/src/test/java/org/zwobble/mammoth/tests/MammothTests.java b/src/test/java/org/zwobble/mammoth/tests/MammothTests.java
index 85a20ce..a072185 100644
--- a/src/test/java/org/zwobble/mammoth/tests/MammothTests.java
+++ b/src/test/java/org/zwobble/mammoth/tests/MammothTests.java
@@ -121,6 +121,29 @@ public class MammothTests {
}
@Test
+ public void warnIfDocumentHasImagesStoredOutsideOfDocumentWhenExternalFileAccessIsDisabled() throws IOException {
+ Path tempDirectory = Files.createTempDirectory("mammoth-");
+ try {
+ Path documentPath = tempDirectory.resolve("external-picture.docx");
+ Files.copy(TestData.file("external-picture.docx").toPath(), documentPath);
+ Files.copy(TestData.file("tiny-picture.png").toPath(), tempDirectory.resolve("tiny-picture.png"));
+ assertThat(
+ new DocumentConverter()
+ .disableExternalFileAccess()
+ .convertToHtml(documentPath.toFile()),
+ allOf(
+ hasProperty("value", equalTo("")),
+ hasProperty("warnings", contains(
+ equalTo("could not open external image 'tiny-picture.png': external file access is disabled")
+ ))
+ )
+ );
+ } finally {
+ tempDirectory.toFile().delete();
+ }
+ }
+
+ @Test
public void warnIfImagesStoredOutsideOfDocumentAreNotFound() throws IOException {
Path tempDirectory = Files.createTempDirectory("mammoth-");
try {
diff --git a/src/test/java/org/zwobble/mammoth/tests/docx/DocumentReaderTests.java b/src/test/java/org/zwobble/mammoth/tests/docx/DocumentReaderTests.java
index 1c07679..db24404 100644
--- a/src/test/java/org/zwobble/mammoth/tests/docx/DocumentReaderTests.java
+++ b/src/test/java/org/zwobble/mammoth/tests/docx/DocumentReaderTests.java
@@ -38,30 +38,47 @@ public class DocumentReaderTests {
@Test
public void mainDocumentIsFoundUsingPackageRelationships() {
- InternalResult<Document> result = DocumentReader.readDocument(Optional.empty(), InMemoryArchive.fromStrings(map(
+ Archive archive = InMemoryArchive.fromStrings(map(
"word/document2.xml", XmlWriter.toString(
- element("w:document", list(
- element("w:body", list(
- element("w:p", list(
- element("w:r", list(
- element("w:t", list(text("Hello.")))
- ))
- ))
- ))
- )),
+ element(
+ "w:document", list(
+ element(
+ "w:body", list(
+ element(
+ "w:p", list(
+ element(
+ "w:r", list(
+ element("w:t", list(text("Hello.")))
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ ),
mainDocumentNamespaces
),
"_rels/.rels", XmlWriter.toString(
- element("Relationships", list(
- element("Relationship", map(
- "Id", "rId1",
- "Target", "/word/document2.xml",
- "Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
- ))
- )),
+ element(
+ "Relationships", list(
+ element(
+ "Relationship", map(
+ "Id", "rId1",
+ "Target", "/word/document2.xml",
+ "Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
+ )
+ )
+ )
+ ),
relationshipsNamespaces
)
- )));
+ ));
+ InternalResult<Document> result = DocumentReader.readDocument(
+ Optional.empty(),
+ archive,
+ false
+ );
assertThat(result, isInternalSuccess(document(
withChildren(paragraphWithText("Hello."))
@@ -84,7 +101,7 @@ public class DocumentReaderTests {
));
PassThroughException exception = assertThrows(
PassThroughException.class,
- () -> DocumentReader.readDocument(Optional.empty(), archive)
+ () -> DocumentReader.readDocument(Optional.empty(), archive, false)
);
assertThat(exception.getMessage(), equalTo("java.io.IOException: Could not find main document part. Are you sure this is a valid .docx file?"));
}