Skip to content

[Question] Concern about RkRecordHandler being unreachable #1252

Open

Description

@nkuprins

Context

#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 (record instanceof RKRecord) {
    return RecordFactory.convertToNumberRecord((RKRecord) record);
}

if (record instanceof MulRKRecord) {
    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!"
  • RecordFactory.convertToNumberRecord: "RK record is a slightly smaller alternative to NumberRecord. POI likes NumberRecord better"

Reproduction

In a debugger, with these breakpoints:

  1. POI RecordFactoryInputStream.java:312: the RK to NumberRecord conversion
  2. Fesod RkRecordHandler.java:41
  3. Fesod NumberRecordHandler.java:45

Question

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?

Activity

  1. Mikkey-f commented on Oct 22, 2026

    @Mikkey-f
    Contributor

    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:

    • 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 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:

    • BOF, RK(42), EOF → listener receives RKRecord (unconverted)
    • BOF, WRITEPROTECT, RK(42), EOF → listener receives RKRecord (the 3rd record is also buffered)
    • BOF, DATEMODE, RK(42), EOF → listener receives NumberRecord value=42.0

    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.
    • Fix#1213 decode XLS RKRecord numeric cells instead of treating them as empty #1214's test calls the handler directly, so it does not demonstrate the reported data loss or exercise the read path. The decode change itself is harmless defensive coding.

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions