Bugfix Issue 1736 ReqSpecEdit Unrenderable Action - sebiboga/testlink-upgraded GitHub Wiki

Bugfix: #1736 — legacy reqSpecEdit.php refuses unrenderable doAction with a 302 and no Event Viewer noise

Problem

The legacy Requirement Specification editor lib/requirements/reqSpecEdit.php logged 3 E_WARNING rows and answered a 28-byte Can not process RENDERING!!! body for any doAction that was not a reqSpecCommands method. Worse, internal helper methods of reqSpecCommands were being invoked via method_exists() with the wrong argument count or visibility, producing silent HTTP 500 with 0 bytes and 0 log rows. An array-shaped ?doAction[]=x triggered an uncaught TypeError in the shared input layer, also producing a silent 500.

Root cause

  • Dispatch used method_exists($commandMgr,$pFn) as a whitelist (24 method_exists-visible members, only 18 GUI actions). The matched method was called with a fixed 2-argument signature regardless of its arity/visibility.
  • inputparameter.class.php:295 calls trim() unconditionally on STRING_N values; a non-scalar $_REQUEST['doAction'] causes trim(array) → TypeError before the controller runs.

Fix

All changes in lib/requirements/reqSpecEdit.php:

  • reqSpecEditDropShapedAction() unsets a non-scalar $_REQUEST['doAction'] before R_PARAMS().
  • reqSpecEditActionWhitelist() returns the exact 18-case list of renderGui()'s GUI-rendering switch and is consulted with is_string($pFn) && $pFn !== '' && isset(...) (only whitelisted names are invoked).
  • reqSpecEditRefuseUnknownAction() logs one tLog(..., 'INFO') row with a fixed literal message (attacker-controlled value is deliberately NOT interpolated) and 302s to gui/templates/requirements/reqSpecMgmt.html with tproject_id carried forward. INFO does not persist below WARNING, so crafted URLs cannot write Error/Warning rows.
  • renderGui()'s default: branch null-guards $opObj as defence in depth.

Evidence

Pre-fix (unknown doAction=init)

  • HTTP 200, 28 bytes, body Can not process RENDERING!!!
  • 3 new events rows (all log_level=2, line 147)

Post-fix (unknown doAction=init)

  • HTTP 302 → http://localhost:8082/gui/templates/requirements/reqSpecMgmt.html?tproject_id=<TP>
  • 0 new events rows

Pre-fix (internal helper ?doAction=simpleCompare)

  • HTTP 500, 0 bytes, 0 new events rows

Post-fix (internal helper ?doAction=simpleCompare)

  • HTTP 302, 0 new events rows

Unchanged (real form)

  • ?doAction=create&parentID=<id> → HTTP 200, real form renders (15646 bytes)
  • ?doAction=edit&req_spec_id=<id> → HTTP 200, real form renders (15981 bytes)

Regression

32/32 cases PASS. The whitelist ⇄ renderGui() switch invariant is enforced (18 == 18), and every doAction targeted at reqSpecEdit.php in-repo is whitelisted. Browser verification confirms the 302 lands on the Requirements Specification Management screen with project context intact, while the create form still renders fully.

Notes

The shared trim(array) defect remains in inputparameter.class.php:295 (#1738). Two pre-existing warnings in reqSpecCommands.class.php (deleteFile() missing $argsObj->uploadOp, bulkReqMon() foreach(null) on empty specs) are #1737. The last two method_exists() dispatches (reqEdit.php:41, planMilestonesEdit.php:30) are #1739. None were fixed here (measured identical pre-fix).