{"record":{"id":"3c3f738866f54aaf","repo":"PHPOffice/PhpSpreadsheet","slug":"the-filename-filename-is-not-recognised-as-an-ole","errorCode":null,"errorMessage":"The filename $filename is not recognised as an OLE file","messagePattern":"The filename \\$filename is not recognised as an OLE file","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Reader\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Shared/OLERead.php","lineNumber":81,"sourceCode":"\n    /** @var int[] */\n    private array $possibleLoop = [];\n\n    /**\n     * Read the file.\n     */\n    public function read(string $filename): void\n    {\n        File::assertFile($filename);\n\n        // Get the file identifier\n        // Don't bother reading the whole file until we know it's a valid OLE file\n        $this->data = (string) file_get_contents($filename, false, null, 0, 8);\n\n        // Check OLE identifier\n        $identifierOle = pack('CCCCCCCC', 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1);\n        if ($this->data != $identifierOle) {\n            throw new ReaderException('The filename ' . $filename . ' is not recognised as an OLE file');\n        }\n\n        // Get the file data\n        $this->data = (string) file_get_contents($filename);\n\n        // Total number of sectors used for the SAT\n        $this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);\n\n        // SecID of the first sector of the directory stream\n        $this->rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS);\n\n        // SecID of the first sector of the SSAT (or -2 if not extant)\n        $this->sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS);\n\n        // SecID of the first sector of the MSAT (or -2 if no additional sectors are used)\n        $this->extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS);\n\n        // Total number of sectors used by MSAT","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Shared/OLERead.php#L63-L99","documentation":"Thrown by OLERead::read() when the first 8 bytes of the file do not equal the OLE2 signature D0 CF 11 E0 A1 B1 1A E1. OLERead is the lightweight header parser the Xls reader uses to pull streams out of a legacy .xls; a mismatch means the file is not a legacy binary workbook at all (or is damaged), regardless of its extension.","triggerScenarios":"Reader\\Xls::canRead()/load() on a file whose content is not OLE: an .xlsx (ZIP 'PK' header) renamed to .xls, a CSV/HTML export with an .xls extension, or a zero-byte/failed upload. The code reads exactly 8 bytes via file_get_contents(..., 0, 8) and string-compares them to the packed identifier.","commonSituations":"Hardcoding `new Xls()` because the filename ends in .xls while users upload OOXML or CSV; Excel's 'strict' exports; third-party systems emitting HTML tables named .xls; uploads interrupted so the file is empty.","solutions":["Use IOFactory::createReaderForFile($path) or IOFactory::load($path) so the reader is chosen by content sniffing, not extension.","If you must pick manually, check the magic bytes first: 'PK...' => Xlsx reader, D0CF11E0... => Xls reader, otherwise treat as CSV/other.","Validate upload size > 0 and content type before parsing; reject early with a clear message.","Ask the sender for a re-export when the signature check fails on a file that should be a real .xls."],"exampleFix":"// before\n$spreadsheet = (new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xls())\n    ->load('export.xls'); // is not recognised as an OLE file (it's really CSV)\n\n// after\n$spreadsheet = \\PhpOffice\\PhpSpreadsheet\\IOFactory::load('export.xls');\n// or explicit sniffing:\n$head = (string) file_get_contents('export.xls', false, null, 0, 4);\n$reader = str_starts_with($head, \"\\xD0\\xCF\\x11\\xE0\")\n    ? new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xls()\n    : new \\PhpOffice\\PhpSpreadsheet\\Reader\\Csv();","handlingStrategy":"validation","validationCode":"$head = (string) @file_get_contents($file, false, null, 0, 4);\nif (str_starts_with($head, \"\\xD0\\xCF\\x11\\xE0\")) {\n    $reader = new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xls();\n} elseif (str_starts_with($head, 'PK')) {\n    $reader = new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xlsx();\n} else {\n    $reader = new \\PhpOffice\\PhpSpreadsheet\\Reader\\Csv();\n}","typeGuard":null,"tryCatchPattern":"try { $spreadsheet = (new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xls())->load($file); }\ncatch (\\PhpOffice\\PhpSpreadsheet\\Reader\\Exception $e) {\n    if (str_contains($e->getMessage(), 'not recognised as an OLE file')) {\n        $spreadsheet = \\PhpOffice\\PhpSpreadsheet\\IOFactory::load($file); // content-based retry\n    } else { throw $e; }\n}","preventionTips":["Never hardcode Reader\\Xls for '.xls' filenames — content decides, extension lies.","Centralize upload handling through IOFactory::load()/createReaderForFile().","Reject zero-byte and truncated uploads on size/mime checks before parsing."],"tags":["file-format","xls","ole","validation","phpspreadsheet"],"backgroundTag":"wrong-file-format","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}