You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Question] Concern about RkRecordHandler being unreachable #1252
#1213 reports that RKRecord cells are read as EMPTY. It reproduces the problem by calling RkRecordHandler.processRecord directly with a hand-built RKRecord. I tried to reproduce it with a real .xls file and couldn't, so I'd like to check whether the handler can be reached at all
What I found
POI converts RK records before any listener sees them. In POI, RecordFactoryInputStream.readNextRecord() does this for every record it reads:
if (recordinstanceofRKRecord) {
returnRecordFactory.convertToNumberRecord((RKRecord) record);
}
if (recordinstanceofMulRKRecord) {
Record[] records = RecordFactory.convertRKRecords((MulRKRecord) record);
...
}
HSSFEventFactory reads only through this stream, so XlsSaxAnalyser.processRecord receives a NumberRecord and the cell is handled by NumberRecordHandler.
POI's documentation says the same:
RKRecord: "We support this in READ-ONLY mode. HSSF converts these to NUMBER records"
MulRKRecord: "HSSF just converts this into multiple NUMBER records. READ-ONLY SUPPORT!"
If no file can reach RkRecordHandler, should the RKRecord.sid registration in XlsSaxAnalyser and the handler itself be removed instead of fixed? Or is there a read path I've missed where an RKRecord reaches XlsSaxAnalyser?
Hi @nkuprins — I dug into this on POI 5.5.1 and fesod's event path. Short answer: for every well-formed workbook, RkRecordHandler is unreachable; there is one narrow malformed-stream exception.
Records are converted before any listener runs
fesod's only XLS data path is XlsSaxAnalyser.execute(), which calls HSSFEventFactory.processWorkbookEvents() (XlsSaxAnalyser.java:190-195). That method goes through HSSFEventFactory.genericProcessEvents(), which wraps the stream in RecordFactoryInputStream (HSSFEventFactory.java:166). RecordFactoryInputStream.readNextRecord() converts:
before req.processRecord(r) is called. So RK cells in a real workbook are handled by NumberRecordHandler, which reads both the value and the format (NumberRecordHandler.java:52-69).
The exception: records read during the encryption probe bypass conversion
RecordFactoryInputStream's constructor runs StreamEncryptionInfo, which reads the BOF and then one or two more records to detect FILEPASS/WRITEPROTECT, and puts them in _unreadRecordBuffer (RecordFactoryInputStream.java:49-103). nextRecord() returns buffered records directly, without readNextRecord() (:245-260). I confirmed with a small POI-only probe:
In a valid workbook these early records are globals (INTERFACEHDR, WRITEPROTECT, DATEMODE, …), never cell records, so this window cannot hold an RK cell. A crafted/malformed stream could put one there. If it does, it arrives before any worksheet BOF — fesod has no sheet context yet — so the current EMPTY branch is not what a real read would produce. MulRKRecord is additionally expanded by MissingRecordAwareHSSFListener before the analyser sees it, so RK packs never reach the handler either.
So
The "older BIFF files silently drop RK values" symptom is not reproducible on valid files — POI's own javadoc says the same ("HSSF converts these to NUMBER records").
RkRecordHandler is dead for valid input, but not strictly unreachable (the malformed 2nd/3rd-record window), so removing the registration is not a pure no-op for adversarial input.
I'd suggest re-scoping #1213 to "harden the practically-unreachable handler" rather than data loss, or closing it if you prefer to remove the dead handler instead. Happy to be corrected if there's a read path I missed.
Activity
Mikkey-f commentedon Oct 22, 2026
Hi @nkuprins — I dug into this on POI 5.5.1 and fesod's event path. Short answer: for every well-formed workbook,
RkRecordHandleris unreachable; there is one narrow malformed-stream exception.Records are converted before any listener runs
fesod's only XLS data path is
XlsSaxAnalyser.execute(), which callsHSSFEventFactory.processWorkbookEvents()(XlsSaxAnalyser.java:190-195). That method goes throughHSSFEventFactory.genericProcessEvents(), which wraps the stream inRecordFactoryInputStream(HSSFEventFactory.java:166).RecordFactoryInputStream.readNextRecord()converts:RKRecord→NumberRecord(RecordFactoryInputStream.java:311-313)MulRKRecord→NumberRecord[](:315-321)before
req.processRecord(r)is called. So RK cells in a real workbook are handled byNumberRecordHandler, which reads both the value and the format (NumberRecordHandler.java:52-69).The exception: records read during the encryption probe bypass conversion
RecordFactoryInputStream's constructor runsStreamEncryptionInfo, which reads the BOF and then one or two more records to detect FILEPASS/WRITEPROTECT, and puts them in_unreadRecordBuffer(RecordFactoryInputStream.java:49-103).nextRecord()returns buffered records directly, withoutreadNextRecord()(:245-260). I confirmed with a small POI-only probe:BOF, RK(42), EOF→ listener receivesRKRecord(unconverted)BOF, WRITEPROTECT, RK(42), EOF→ listener receivesRKRecord(the 3rd record is also buffered)BOF, DATEMODE, RK(42), EOF→ listener receivesNumberRecord value=42.0In a valid workbook these early records are globals (INTERFACEHDR, WRITEPROTECT, DATEMODE, …), never cell records, so this window cannot hold an RK cell. A crafted/malformed stream could put one there. If it does, it arrives before any worksheet BOF — fesod has no sheet context yet — so the current EMPTY branch is not what a real read would produce.
MulRKRecordis additionally expanded byMissingRecordAwareHSSFListenerbefore the analyser sees it, so RK packs never reach the handler either.So
RkRecordHandleris dead for valid input, but not strictly unreachable (the malformed 2nd/3rd-record window), so removing the registration is not a pure no-op for adversarial input.I'd suggest re-scoping #1213 to "harden the practically-unreachable handler" rather than data loss, or closing it if you prefer to remove the dead handler instead. Happy to be corrected if there's a read path I missed.