{"record":{"id":"2c4dd86c5b16251e","repo":"PHPOffice/PhpSpreadsheet","slug":"unsupported-pps-type","errorCode":null,"errorMessage":"Unsupported PPS type","messagePattern":"Unsupported PPS type","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Shared/OLE.php","lineNumber":321,"sourceCode":"            // Simple conversion from UTF-16LE to ISO-8859-1\n            $name = str_replace(\"\\x00\", '', $nameUtf16);\n            $type = self::readInt1($fh);\n            switch ($type) {\n                case self::OLE_PPS_TYPE_ROOT:\n                    $pps = new Root(null, null, []);\n                    $this->root = $pps;\n\n                    break;\n                case self::OLE_PPS_TYPE_DIR:\n                    $pps = new OLE\\PPS(null, null, null, null, null, null, null, null, null, []);\n\n                    break;\n                case self::OLE_PPS_TYPE_FILE:\n                    $pps = new OLE\\PPS\\File($name);\n\n                    break;\n                default:\n                    throw new Exception('Unsupported PPS type');\n            }\n            fseek($fh, 1, SEEK_CUR);\n            $pps->Type = $type;\n            $pps->Name = $name;\n            $pps->PrevPps = self::readInt4($fh);\n            $pps->NextPps = self::readInt4($fh);\n            $pps->DirPps = self::readInt4($fh);\n            fseek($fh, 20, SEEK_CUR);\n            $pps->Time1st = self::OLE2LocalDate((string) fread($fh, 8));\n            $pps->Time2nd = self::OLE2LocalDate((string) fread($fh, 8));\n            $pps->startBlock = self::readInt4($fh);\n            $pps->Size = self::readInt4($fh);\n            $pps->No = count($this->_list);\n            $this->_list[] = $pps;\n\n            // check if the PPS tree (starting from root) is complete\n            if (isset($this->root) && $this->ppsTreeComplete($this->root->No)) {\n                break;","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Shared/OLE.php#L303-L339","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n$spreadsheet = (new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xls())->load('batch.xls');\n// Unsupported PPS type\n\n// after\ntry {\n    $spreadsheet = (new \\PhpOffice\\PhpSpreadsheet\\Reader\\Xls())->load('batch.xls');\n} catch (\\PhpOffice\\PhpSpreadsheet\\Reader\\Exception $e) {\n    logger()->warning('Rejecting malformed workbook', ['file' => 'batch.xls', 'err' => $e->getMessage()]);\n    return response()->unprocessableEntity('The uploaded workbook is damaged or unsupported.');\n}","handlingStrategy":"try-catch","validationCode":"$head = (string) @file_get_contents($file, false, null, 0, 8);\nif ($head !== \"\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1\") {\n    throw new RuntimeException('Not a legacy .xls workbook');\n}","typeGuard":null,"tryCatchPattern":"try { $spreadsheet = $reader->load($file); }\ncatch (\\PhpOffice\\PhpSpreadsheet\\Reader\\Exception $e) {\n    if (in_array($e->getMessage(), ['Unsupported PPS type', 'Detected loop while iterating blocks', 'Expecting 8 byte string'], true)\n        || str_contains($e->getMessage(), 'invalid.')) {\n        return ['error' => 'The workbook file is corrupt. Please re-export and re-upload.'];\n    }\n    throw $e;\n}","preventionTips":["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."],"tags":["ole","file-corruption","xls","phpspreadsheet"],"backgroundTag":"corrupted-binary-structure","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}