Skip to content

fix(API): prevent TypeError and handle integer overflow in paginate() - #10764

Open
gr8man wants to merge 2 commits into
codeigniter4:developfrom
gr8man:fix/api-paginate-overflow
Open

gr8man wants to merge 2 commits into
codeigniter4:developfrom
gr8man:fix/api-paginate-overflow

Conversation

@gr8man

@gr8man gr8man commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Description

When ?page= contains a very large value (e.g. PHP_INT_MAX), the Query Builder branch of ResponseTrait::paginate() calculates the offset as:

$offset = ($page - 1) * $perPage;

That multiplication overflows the int type and becomes a float. Because BaseBuilder::limit() is declared as limit(?int $limit = null, ?int $offset = null), passing the float raises a TypeError, and paginate() turns it into a 500 response (RESTful.paginateError).

This PR clamps the requested page to the available range before the offset is calculated:

$total      = (clone $resource)->countAllResults();
$totalPages = (int) ceil($total / $perPage);
$page       = max(1, min($page, max(1, $totalPages)));
$offset     = ($page - 1) * $perPage;

Because $page can no longer exceed $totalPages, $offset stays bounded by $total and cannot overflow the int type. It also makes the Query Builder path consistent with the Model path, where the Pager already limits currentPage to pageCount, and with the links, which were already clamped to the available range. meta.page now reports the clamped page instead of the out-of-range one.

The new regression tests cover both a page overflow with results (page clamped to the last page) and with an empty result set (page clamped to 1, offset 0). Without the fix they fail, because the response has no meta key (the TypeError was converted into an error response).

No user guide change: the documented behaviour only defines in-range pages, and the clamped page now matches the already-clamped links.

Checklist:

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value (without duplication)
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

When a huge page number (e.g. PHP_INT_MAX) is requested via ?page=, the
offset calculation `($page - 1) * $perPage` overflows the int type and
becomes a float. Passing that float to `BaseBuilder::limit(?int, ?int)`
raises a TypeError, which `paginate()` reports as a 500 response
("Paginate Error").

Clamp the requested page to the available range, so the offset always
stays within the int type:

    $page = max(1, min($page, max(1, $totalPages)));

This also aligns the Query Builder path with the Pager used by the Model
path, which already limits the current page to the page count.
@carson-codeigniter4 carson-codeigniter4 Bot added the bug Verified issues on the current code behavior or pull requests that will fix them label Nov 11, 2026

This branch has not been deployed

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

Labels

bug Verified issues on the current code behavior or pull requests that will fix them

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant