PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Reader\Exception

File doesn't seem to be an OLE container.

Error message

File doesn't seem to be an OLE container.

What it means

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.

Source

Thrown at src/PhpSpreadsheet/Shared/OLE.php:121

    /**
     * Reads an OLE container from the contents of the file given.
     *
     * @acces public
     *
     * @return bool true on success, PEAR_Error on failure
     */
    public function read(string $filename): bool
    {
        $fh = @fopen($filename, 'rb');
        if ($fh === false) {
            throw new ReaderException("Can't open file $filename");
        }
        $this->_file_handle = $fh;

        $signature = fread($fh, 8);
        if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
            throw new ReaderException("File doesn't seem to be an OLE container.");
        }
        fseek($fh, 28);
        if (fread($fh, 2) != "\xFE\xFF") {
            // This shouldn't be a problem in practice
            throw new ReaderException('Only Little-Endian encoding is supported.');
        }
        // Size of blocks and short blocks in bytes
        /** @var int<1, max> */
        $temp = 2 ** self::readInt2($fh);
        $this->bigBlockSize = $temp;
        $this->smallBlockSize = 2 ** self::readInt2($fh);

        // Skip UID, revision number and version number
        fseek($fh, 44);
        // Number of blocks in Big Block Allocation Table
        $bbatBlockCount = self::readInt4($fh);

        // Root chain 1st block

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Detect the real format before parsing: use IOFactory::createReaderForFile($file) (it sniffs content) or IOFactory::load($file), instead of hardcoding the Xls/OLE reader.
  2. Sniff the magic bytes yourself: an .xls must start with D0 CF 11 E0; a ZIP header ('PK') means OOXML (.xlsx), so route accordingly.
  3. Re-export or re-download the source file if the header is corrupted; validate uploads by content, not by extension.
  4. If you expect CSV input, parse it with the Csv reader explicitly instead of the Xls path.

Example fix

// before
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls();
$spreadsheet = $reader->load('upload.xls'); // File doesn't seem to be an OLE container.

// after
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load('upload.xls');
// or route on the actual signature:
$sig = bin2hex((string) fread(fopen('upload.xls', 'rb'), 8));
$reader = ($sig === 'd0cf11e0a1b11ae1') ? new Xls() : new Xlsx();
Defensive patterns

Strategy: validation

Validate before calling

function looksLikeOle(string $file): bool
{
    $fh = fopen($file, 'rb');
    $sig = $fh ? (string) fread($fh, 8) : '';
    return $sig === "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1";
}
if (!looksLikeOle($file)) { /* route to Xlsx/Csv reader or reject */ }

Try / catch

try { $spreadsheet = $reader->load($file); }
catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
    if (str_contains($e->getMessage(), 'OLE container')) {
        // wrong format: re-route by content (IOFactory::load) or ask for a re-export
        $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file);
    } else { throw $e; }
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/851e2eb98ccd3584. Report an issue: GitHub.