{"record":{"id":"851e2eb98ccd3584","repo":"PHPOffice/PhpSpreadsheet","slug":"file-doesn-t-seem-to-be-an-ole-container","errorCode":null,"errorMessage":"File doesn't seem to be an OLE container.","messagePattern":"File doesn't seem to be an OLE container\\.","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Reader\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Shared/OLE.php","lineNumber":121,"sourceCode":"\n    /**\n     * Reads an OLE container from the contents of the file given.\n     *\n     * @acces public\n     *\n     * @return bool true on success, PEAR_Error on failure\n     */\n    public function read(string $filename): bool\n    {\n        $fh = @fopen($filename, 'rb');\n        if ($fh === false) {\n            throw new ReaderException(\"Can't open file $filename\");\n        }\n        $this->_file_handle = $fh;\n\n        $signature = fread($fh, 8);\n        if (\"\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1\" != $signature) {\n            throw new ReaderException(\"File doesn't seem to be an OLE container.\");\n        }\n        fseek($fh, 28);\n        if (fread($fh, 2) != \"\\xFE\\xFF\") {\n            // This shouldn't be a problem in practice\n            throw new ReaderException('Only Little-Endian encoding is supported.');\n        }\n        // Size of blocks and short blocks in bytes\n        /** @var int<1, max> */\n        $temp = 2 ** self::readInt2($fh);\n        $this->bigBlockSize = $temp;\n        $this->smallBlockSize = 2 ** self::readInt2($fh);\n\n        // Skip UID, revision number and version number\n        fseek($fh, 44);\n        // Number of blocks in Big Block Allocation Table\n        $bbatBlockCount = self::readInt4($fh);\n\n        // Root chain 1st block","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Shared/OLE.php#L103-L139","documentation":"Thrown by Shared\\OLE::read() when the first 8 bytes of the file are not the OLE2 compound-document signature \\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1. That signature prefixes legacy Microsoft binary formats (.xls, .doc, .ppt, .msi); anything else — ZIP-based .xlsx, CSV, HTML, plain text — is rejected immediately.","triggerScenarios":"Feeding a non-OLE file into the .xls reading path: passing an .xlsx (starts with 'PK'), a CSV, or an HTML table saved with an .xls extension to code that parses it as an OLE container; or a truncated/corrupted .xls whose header was overwritten. The check is a strict byte comparison of fread($fh, 8).","commonSituations":"Users renaming report.xlsx to report.xls (or exports mislabeled by mime-type); code hardcoding Reader\\Xls / OLE parsing for any .xls-named upload; files corrupted in transfer or truncated by upload limits; CSV data with an .xls extension coming from third-party ERP exports.","solutions":["Detect the real format before parsing: use IOFactory::createReaderForFile($file) (it sniffs content) or IOFactory::load($file), instead of hardcoding the Xls/OLE reader.","Sniff the magic bytes yourself: an .xls must start with D0 CF 11 E0; a ZIP header ('PK') means OOXML (.xlsx), so route accordingly.","Re-export or re-download the source file if the header is corrupted; validate uploads by content, not by extension.","If you expect CSV input, parse it with the Csv reader explicitly instead of the Xls path."],"exampleFix":"// before\n$reader = new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xls();\n$spreadsheet = $reader->load('upload.xls'); // File doesn't seem to be an OLE container.\n\n// after\n$spreadsheet = \\PhpOffice\\PhpSpreadsheet\\IOFactory::load('upload.xls');\n// or route on the actual signature:\n$sig = bin2hex((string) fread(fopen('upload.xls', 'rb'), 8));\n$reader = ($sig === 'd0cf11e0a1b11ae1') ? new Xls() : new Xlsx();","handlingStrategy":"validation","validationCode":"function looksLikeOle(string $file): bool\n{\n    $fh = fopen($file, 'rb');\n    $sig = $fh ? (string) fread($fh, 8) : '';\n    return $sig === \"\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1\";\n}\nif (!looksLikeOle($file)) { /* route to Xlsx/Csv reader or reject */ }","typeGuard":null,"tryCatchPattern":"try { $spreadsheet = $reader->load($file); }\ncatch (\\PhpOffice\\PhpSpreadsheet\\Reader\\Exception $e) {\n    if (str_contains($e->getMessage(), 'OLE container')) {\n        // wrong format: re-route by content (IOFactory::load) or ask for a re-export\n        $spreadsheet = \\PhpOffice\\PhpSpreadsheet\\IOFactory::load($file);\n    } else { throw $e; }\n}","preventionTips":["Choose readers by magic bytes, never by file extension.","Use IOFactory::createReaderForFile()/load() as the default entry point for arbitrary uploads.","Tell users to export real .xls/.xlsx rather than renaming files, and validate on upload."],"tags":["file-format","ole","xls","validation","phpspreadsheet"],"backgroundTag":"invalid-file-signature","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}