PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Exception
Unsupported PPS type
Error message
Unsupported PPS type
What it means
Thrown while Shared\OLE parses the property-set (PPS) directory of a compound document: each entry's type field must be 1 (directory), 2 (file/stream) or 5 (root), and anything else hits the default branch that raises this exception. An out-of-range type byte means the directory structure is malformed.
Source
Thrown at src/PhpSpreadsheet/Shared/OLE.php:321
// Simple conversion from UTF-16LE to ISO-8859-1
$name = str_replace("\x00", '', $nameUtf16);
$type = self::readInt1($fh);
switch ($type) {
case self::OLE_PPS_TYPE_ROOT:
$pps = new Root(null, null, []);
$this->root = $pps;
break;
case self::OLE_PPS_TYPE_DIR:
$pps = new OLE\PPS(null, null, null, null, null, null, null, null, null, []);
break;
case self::OLE_PPS_TYPE_FILE:
$pps = new OLE\PPS\File($name);
break;
default:
throw new Exception('Unsupported PPS type');
}
fseek($fh, 1, SEEK_CUR);
$pps->Type = $type;
$pps->Name = $name;
$pps->PrevPps = self::readInt4($fh);
$pps->NextPps = self::readInt4($fh);
$pps->DirPps = self::readInt4($fh);
fseek($fh, 20, SEEK_CUR);
$pps->Time1st = self::OLE2LocalDate((string) fread($fh, 8));
$pps->Time2nd = self::OLE2LocalDate((string) fread($fh, 8));
$pps->startBlock = self::readInt4($fh);
$pps->Size = self::readInt4($fh);
$pps->No = count($this->_list);
$this->_list[] = $pps;
// check if the PPS tree (starting from root) is complete
if (isset($this->root) && $this->ppsTreeComplete($this->root->No)) {
break;View on GitHub (pinned to 65b080eef4)
Solutions
- Re-acquire the file from its source and retry; verify with another reader (Excel/LibreOffice) that the workbook itself opens.
- Validate uploads by content sniffing plus a size sanity check before invoking spreadsheet parsing, so obviously malformed files are rejected earlier.
- Catch PhpSpreadsheet\Exception around load and convert to a user-facing 'unsupported or damaged file' result rather than a 500.
- Keep the library current — hardening for malformed OLE structures lands in patch releases.
Example fix
// before
$spreadsheet = (new \PhpOffice\PhpSpreadsheet\Reader\Xls())->load('batch.xls');
// Unsupported PPS type
// after
try {
$spreadsheet = (new \PhpOffice\PhpSpreadsheet\Reader\Xls())->load('batch.xls');
} catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
logger()->warning('Rejecting malformed workbook', ['file' => 'batch.xls', 'err' => $e->getMessage()]);
return response()->unprocessableEntity('The uploaded workbook is damaged or unsupported.');
} Defensive patterns
Strategy: try-catch
Validate before calling
$head = (string) @file_get_contents($file, false, null, 0, 8);
if ($head !== "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1") {
throw new RuntimeException('Not a legacy .xls workbook');
} Try / catch
try { $spreadsheet = $reader->load($file); }
catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
if (in_array($e->getMessage(), ['Unsupported PPS type', 'Detected loop while iterating blocks', 'Expecting 8 byte string'], true)
|| str_contains($e->getMessage(), 'invalid.')) {
return ['error' => 'The workbook file is corrupt. Please re-export and re-upload.'];
}
throw $e;
} Prevention
- Screen uploads (size > 0, OLE magic bytes) before parsing user-supplied workbooks.
- Keep PhpSpreadsheet updated to pick up malformed-container hardening.
- Convert legacy .xls inputs to .xlsx at ingest time with a trusted tool if your pipeline only needs the data.
When it happens
Trigger: OLE::read() iterating PPS entries (in _readPpsWks) over a .xls whose directory stream is damaged, so the byte interpreted as the type is garbage (0, or >5). Reached only after the OLE signature and FAT parsing succeeded, so the container header was plausible but the directory is corrupt.
Common situations: Truncated downloads where the directory sector is incomplete; files damaged by antivirus sanitization or partial writes; malformed .xls produced by buggy third-party generators; fuzzed/hostile uploads designed to crash parsers.
Related errors
- Only Little-Endian encoding is supported.
- File doesn't seem to be an OLE container.
- Unable to open stream $path
- Expecting 8 byte string
- The filename $filename is not recognised as an OLE file
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/2c4dd86c5b16251e.
Report an issue: GitHub.