diff options
| -rw-r--r-- | NEWS | 2 | ||||
| -rw-r--r-- | README.md | 13 | ||||
| -rw-r--r-- | mammoth/__init__.py | 19 | ||||
| -rw-r--r-- | mammoth/docx/__init__.py | 10 | ||||
| -rw-r--r-- | mammoth/docx/files.py | 14 | ||||
| -rw-r--r-- | tests/docx/files_tests.py | 21 | ||||
| -rw-r--r-- | tests/mammoth_tests.py | 8 |
7 files changed, 73 insertions, 14 deletions
@@ -4,6 +4,8 @@ * Fix conversion of unmerged table cells. +* Support disabling external file accesses using the external_file_access argument. + # 1.10.0 * Add "Heading" and "Body" styles, as found in documents created by Apple Pages, @@ -178,6 +178,16 @@ pass `include_default_style_map=False`: result = mammoth.convert_to_html(docx_file, style_map=style_map, include_default_style_map=False) ``` +#### External file access + +Source documents may reference files outside of the source document. +To disable access to any such external files during the conversion process, +pass `external_file_access=False`: + +```javascript +result = mammoth.convert_to_html(docx_file, external_file_access=False) +``` + #### Custom image handlers By default, images are converted to `<img>` elements with the source included inline in the `src` attribute. @@ -415,6 +425,9 @@ For instance: and embed the HTML into your website, this may allow arbitrary files on the server to be read and exfiltrated. + To disable access to any such external files during the conversion process, + pass `external_file_access=False`. + ### Document transforms **The API for document transforms should be considered unstable, diff --git a/mammoth/__init__.py b/mammoth/__init__.py index 27cc580..e8d3a83 100644 --- a/mammoth/__init__.py +++ b/mammoth/__init__.py @@ -16,15 +16,28 @@ def convert_to_markdown(*args, **kwargs): return convert(*args, output_format="markdown", **kwargs) -def convert(fileobj, transform_document=None, id_prefix=None, include_embedded_style_map=_undefined, **kwargs): +def convert( + fileobj, + transform_document=None, + id_prefix=None, + include_embedded_style_map=_undefined, + external_file_access=_undefined, + **kwargs +): if include_embedded_style_map is _undefined: include_embedded_style_map = True + if transform_document is None: transform_document = lambda x: x + if include_embedded_style_map: kwargs["embedded_style_map"] = read_style_map(fileobj) + + if external_file_access is _undefined: + external_file_access = True + return options.read_options(kwargs).bind(lambda convert_options: - docx.read(fileobj).map(transform_document).bind(lambda document: + docx.read(fileobj, external_file_access=external_file_access).map(transform_document).bind(lambda document: conversion.convert_document_element_to_html( document, id_prefix=id_prefix, @@ -32,7 +45,7 @@ def convert(fileobj, transform_document=None, id_prefix=None, include_embedded_s ) ) ) - + def extract_raw_text(fileobj): return docx.read(fileobj).map(extract_raw_text_from_element) diff --git a/mammoth/docx/__init__.py b/mammoth/docx/__init__.py index 3848736..a0576cf 100644 --- a/mammoth/docx/__init__.py +++ b/mammoth/docx/__init__.py @@ -19,13 +19,14 @@ from ..zips import open_zip _empty_result = results.success([]) -def read(fileobj): +def read(fileobj, external_file_access=False): zip_file = open_zip(fileobj, "r") part_paths = _find_part_paths(zip_file) read_part_with_body = _part_with_body_reader( getattr(fileobj, "name", None), zip_file, part_paths=part_paths, + external_file_access=external_file_access, ) return results.combine([ @@ -134,7 +135,7 @@ def _read_document(zip_file, read_part_with_body, notes, comments, part_paths): ) -def _part_with_body_reader(document_path, zip_file, part_paths): +def _part_with_body_reader(document_path, zip_file, part_paths, external_file_access): content_types = _try_read_entry_or_default( zip_file, "[Content_Types].xml", @@ -156,7 +157,10 @@ def _part_with_body_reader(document_path, zip_file, part_paths): default=Numbering.EMPTY, ) - files = Files(None if document_path is None else os.path.dirname(document_path)) + files = Files( + None if document_path is None else os.path.dirname(document_path), + external_file_access=external_file_access, + ) def read_part(name, reader, default=_undefined): relationships = _read_relationships(zip_file, _find_relationships_path_for(name)) diff --git a/mammoth/docx/files.py b/mammoth/docx/files.py index 61cf8d5..e26a43f 100644 --- a/mammoth/docx/files.py +++ b/mammoth/docx/files.py @@ -11,10 +11,16 @@ except ImportError: class Files(object): - def __init__(self, base): + def __init__(self, base, external_file_access): self._base = base - + self._external_file_access = external_file_access + def open(self, uri): + if not self._external_file_access: + raise ExternalFileAccessIsDisabledError( + "could not open external image '{0}', external file access is disabled".format(uri) + ) + try: if _is_absolute(uri): return contextlib.closing(urlopen(uri)) @@ -34,3 +40,7 @@ def _is_absolute(url): class InvalidFileReferenceError(ValueError): pass + + +class ExternalFileAccessIsDisabledError(InvalidFileReferenceError): + pass diff --git a/tests/docx/files_tests.py b/tests/docx/files_tests.py index 6845cc2..b405373 100644 --- a/tests/docx/files_tests.py +++ b/tests/docx/files_tests.py @@ -1,10 +1,19 @@ -from mammoth.docx.files import Files, InvalidFileReferenceError +from mammoth.docx.files import ExternalFileAccessIsDisabledError, Files, InvalidFileReferenceError from ..testing import generate_test_path, assert_equal, assert_raises +def test_when_external_file_access_is_disabled_then_opening_file_raises_error(): + files = Files(None, external_file_access=False) + error = assert_raises(ExternalFileAccessIsDisabledError, lambda: files.open("/tmp/image.png")) + expected_message = ( + "could not open external image '/tmp/image.png', external file access is disabled" + ) + assert_equal(expected_message, str(error)) + + def test_can_open_files_with_file_uri(): path = generate_test_path("tiny-picture.png") - files = Files(None) + files = Files(None, external_file_access=True) with files.open("file:///" + path) as image_file: contents = image_file.read() assert_equal(bytes, type(contents)) @@ -13,7 +22,7 @@ def test_can_open_files_with_file_uri(): def test_can_open_files_with_relative_uri(): - files = Files(generate_test_path("")) + files = Files(generate_test_path(""), external_file_access=True) with files.open("tiny-picture.png") as image_file: contents = image_file.read() assert_equal(bytes, type(contents)) @@ -22,7 +31,7 @@ def test_can_open_files_with_relative_uri(): def test_given_base_is_not_set_when_opening_relative_uri_then_error_is_raised(): - files = Files(None) + files = Files(None, external_file_access=True) error = assert_raises(InvalidFileReferenceError, lambda: files.open("not-a-real-file.png")) expected_message = ( "could not find external image 'not-a-real-file.png', fileobj has no name" @@ -31,7 +40,7 @@ def test_given_base_is_not_set_when_opening_relative_uri_then_error_is_raised(): def test_error_is_raised_if_relative_uri_cannot_be_opened(): - files = Files("/tmp") + files = Files("/tmp", external_file_access=True) error = assert_raises(InvalidFileReferenceError, lambda: files.open("not-a-real-file.png")) expected_message = ( "could not open external image: 'not-a-real-file.png' (document directory: '/tmp')\n" + @@ -41,7 +50,7 @@ def test_error_is_raised_if_relative_uri_cannot_be_opened(): def test_error_is_raised_if_file_uri_cannot_be_opened(): - files = Files("/tmp") + files = Files("/tmp", external_file_access=True) error = assert_raises(InvalidFileReferenceError, lambda: files.open("file:///not-a-real-file.png")) expected_message = "could not open external image: 'file:///not-a-real-file.png' (document directory: '/tmp')\n" assert str(error).startswith(expected_message) diff --git a/tests/mammoth_tests.py b/tests/mammoth_tests.py index 36d0e83..a7308f8 100644 --- a/tests/mammoth_tests.py +++ b/tests/mammoth_tests.py @@ -127,6 +127,14 @@ def test_warn_if_images_stored_outside_of_document_are_specified_when_passing_fi assert_equal([results.warning("could not find external image 'tiny-picture.png', fileobj has no name")], result.messages) +def test_warn_if_images_stored_outside_of_document_are_specified_when_external_file_access_is_disabled(): + with open(generate_test_path("external-picture.docx"), "rb") as fileobj: + result = mammoth.convert_to_html(fileobj=fileobj, external_file_access=False) + + assert_equal("", result.value) + assert_equal([results.warning("could not open external image 'tiny-picture.png', external file access is disabled")], result.messages) + + def test_warn_if_images_stored_outside_of_document_are_not_found(): with tempman.create_temp_dir() as temp_dir: document_path = os.path.join(temp_dir.path, "document.docx") |
