toBeString(); function _tree_fn_body(string $src, string $fn) : string { $start = strpos($src, "function $fn("); expect($start)->not->toBeFalse(); $end = strpos($src, "\nfunction ", $start + 1); return substr($src, $start, ($end === false ? strlen($src) : $end) - $start); } // the first statement that writes tree data in each function $cases = array( 'api_tree_create_node' => array('bail' => 'return false;', 'sink' => 'sql_save('), 'api_tree_delete_node' => array('bail' => 'return;', 'sink' => 'db_execute'), 'api_tree_rename_node' => array('bail' => 'return;', 'sink' => 'db_execute'), ); foreach ($cases as $fn => $case) { test("$fn refuses trees the user may not access", function () use ($src, $fn, $case) { $body = _tree_fn_body($src, $fn); // the permission guard exists and bails immediately inside its own block, // so an unrelated early return elsewhere can't be mistaken for the guard $guard = strpos($body, 'if (!is_tree_allowed($tree_id)) {'); expect($guard)->not->toBeFalse(); $block = substr($body, $guard, strpos($body, '}', $guard) - $guard); expect($block)->toContain($case['bail']); // the guard runs before the first write to the tree $sink = strpos($body, $case['sink']); if ($sink !== false) { expect($guard)->toBeLessThan($sink); } }); test("$fn rejects an out-of-range tree_id before the permission lookup", function () use ($src, $fn) { $body = _tree_fn_body($src, $fn); // is_tree_allowed() caches into $_SESSION and hits the database, so a // 0/negative id must fail as bad input first $idcheck = strpos($body, 'if ($tree_id <= 0) {'); $guard = strpos($body, 'if (!is_tree_allowed($tree_id)) {'); expect($idcheck)->not->toBeFalse(); expect($guard)->not->toBeFalse(); expect($idcheck)->toBeLessThan($guard); }); }