{"record":{"id":"33aece80e52c4b1c","repo":"docling-project/docling","slug":"archive-member-ocr-info-path-is-not-a-regular","errorCode":null,"errorMessage":"Archive member '{ocr_info.path}' is not a regular file (directory or symlink in tar).","messagePattern":"Archive member '(.+?)' is not a regular file \\(directory or symlink in tar\\)\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"docling/backend/mets_gbs_backend.py","lineNumber":411,"sourceCode":"        image_data = image_file.read(self.options.max_file_bytes + 1)\n        if len(image_data) > self.options.max_file_bytes:\n            raise ValueError(\n                f\"Image file {image_info.path} exceeds individual file size limit of {self.options.max_file_bytes} bytes\"\n            )\n\n        # Security: Track total bytes extracted\n        self._total_bytes_extracted += len(image_data)\n        if self._total_bytes_extracted > self.options.max_total_bytes:\n            raise ValueError(\n                f\"Total extracted data exceeds maximum limit of {self.options.max_total_bytes} bytes\"\n            )\n\n        buf = BytesIO(image_data)\n        im: PILImage = Image.open(buf)\n\n        ocr_file = self._tar.extractfile(ocr_info.path)\n        if ocr_file is None:\n            raise RuntimeError(\n                f\"Archive member '{ocr_info.path}' is not a regular file \"\n                \"(directory or symlink in tar).\"\n            )\n        ocr_file = cast(tarfile.ExFileObject, ocr_file)\n        ocr_content = ocr_file.read(self.options.max_file_bytes + 1)\n        if len(ocr_content) > self.options.max_file_bytes:\n            raise ValueError(\n                f\"OCR file {ocr_info.path} exceeds individual file size limit of {self.options.max_file_bytes} bytes\"\n            )\n\n        # Security: Track total bytes extracted\n        self._total_bytes_extracted += len(ocr_content)\n        if self._total_bytes_extracted > self.options.max_total_bytes:\n            raise ValueError(\n                f\"Total extracted data exceeds maximum limit of {self.options.max_total_bytes} bytes\"\n            )\n\n        parser = etree.HTMLParser(no_network=True)","sourceCodeStart":393,"sourceCodeEnd":429,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/backend/mets_gbs_backend.py#L393-L429","documentation":"RuntimeError raised when tarfile.extractfile() returns None for the member named by the page's 'coordOCR' fileGrp entry — meaning that member is a directory, symlink, or other non-regular file. It is the OCR-side twin of the image-member check: METS metadata must point at a real, extractable file, and link/directory members are rejected as both invalid and a potential tar-based attack.","triggerScenarios":"Converting a METS GBS archive whose coordOCR entries reference symlinks/directories, or whose OCR member names do not exactly match the tar member names so extractfile resolves to nothing openable.","commonSituations":"Repacked archives that turned files into symlinks, case-sensitivity mismatches (Ocr.XML vs ocr.xml) introduced by repacking on case-insensitive filesystems, or edited METS files with wrong fileGrp paths.","solutions":["Repack from an extracted tree so all members are regular files.","Verify OCR member names in the tar match the FLocat hrefs in the METS file exactly (case included).","Regenerate the METS package from the source digitization tool.","Treat failures on untrusted archives as malicious input and quarantine the file."],"exampleFix":"# before\nresult = converter.convert(mets_path)  # RuntimeError: coordOCR not regular\n\n# after\nimport tarfile\nwith tarfile.open(mets_path) as t:\n    names = {m.name: m for m in t.getmembers()}\n    for href in mets_ocr_hrefs:  # FLocat xlink:href values\n        assert href in names and names[href].isfile(), href\nresult = converter.convert(mets_path)","handlingStrategy":"validation","validationCode":"import tarfile\n\ndef ocr_members_are_regular(tar_path: str) -> bool:\n    with tarfile.open(tar_path) as t:\n        ocr = [m for m in t.getmembers() if 'ocr' in m.name.lower() or m.name.endswith(('.html', '.htm'))]\n        return all(m.isfile() for m in ocr) if ocr else True","typeGuard":null,"tryCatchPattern":"try:\n    result = converter.convert(mets_path)\nexcept RuntimeError as e:\n    if 'not a regular file' in str(e):\n        log.error('coordOCR member is a link/dir in %s — repack from extracted tree', mets_path)","preventionTips":["Repack archives from fully extracted trees so OCR members are regular files.","Check exact-match (case-sensitive) member names against METS FLocat hrefs.","Regenerate packages with the digitization tool rather than editing tar members by hand."],"tags":["mets","archive","security","tar","symlink","ocr"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}