Skip to content

Harden web-logbook server against malformed pagination query params#585

Merged
patrickrb merged 1 commit into
devfrom
optio/task-a3f984a8-9bc8-4c43-80e3-b337c3984007
Jul 15, 2026
Merged

Harden web-logbook server against malformed pagination query params#585
patrickrb merged 1 commit into
devfrom
optio/task-a3f984a8-9bc8-4c43-80e3-b337c3984007

Conversation

@patrickrb

Copy link
Copy Markdown
Owner

Root cause

The always-on LogHttpServer (started at app launch in MainViewModel, listening on DEFAULT_PORT for the whole session — LAN-reachable) parsed its pagination query parameters with raw Integer.parseInt and clamped the page index only on the high side. Two defects on untrusted input:

  1. Uncaught NumberFormatException on non-numeric params. ?page=abc / ?pageSize=abc / ?session=abc (a hand-edited URL, a stale bookmark, a truncated nav link) threw out of serve(). NanoHTTPD's ClientHandler.run() catches Exception broadly — it logs and closes the socket — so the browser got an aborted/empty response instead of the requested logbook view. (Reliability, not an app crash.)

  2. Negative SQL offset from an un-floored page index. Each list handler did if (pageIndex > pageCount) pageIndex = pageCount; with no < 1 floor. So ?page=0 (or a negative page) left pageIndex < 1, and the paged query's LIMIT (pageIndex - 1) * pageSize, pageSize offset went negative — a wrong/empty result set numbered from a negative "No." column. (Functional bug affecting existing web-logbook behaviour.)

Both stem from the same root: unvalidated pagination query parameters.

Fix

Two pure static helpers, mirroring the file's existing pageCount / normalizePageSize pattern:

  • parseQueryInt(String value, int fallback) — returns fallback when the value is null or not a valid integer (also trims whitespace).
  • clampPageIndex(int pageIndex, int pageCount) — floors to 1 and caps at pageCount (which is always >= 1).

Applied at all three paged views (getMessages, getSWLQsoMessages, getQsoLogs) and both import-session handlers (makeGetImportTaskHTML, doCancelImport, fallback -1 = an id matching no task). Behaviour is byte-identical for well-formed requests; malformed ones now fall back to a sane default (page 1 / default page size) instead of breaking the response.

Testing

  • New LogHttpServerQueryParamTest (pure JVM, JUnit4 + Truth, no Robolectric — mirrors LogHttpServerPageCountTest), 8 cases covering valid/null/non-numeric/whitespace parse and the page-index floor/cap plus the "offset never negative" invariant.
  • Verified the guard cases fail against the pre-fix behaviour (raw parse throws NFE; old high-side-only clamp leaves page=0 → offset -pageSize), pass after.
  • ./gradlew :app:testDebugUnitTest green (full suite).
  • ./gradlew :app:assembleDebug green (4 ABIs, hamlib/NDK).

Risk

Low. Change is confined to LogHttpServer query-param parsing; no DSP/decoder/protocol/interop surface touched. Well-formed requests are unchanged.

The always-on LogHttpServer (started at app launch, LAN-reachable) read the
?page=, ?pageSize= and ?session= query parameters with a raw Integer.parseInt
and clamped pageIndex only on the high side. Two defects on untrusted input:

- A non-numeric value (?page=abc, a hand-edited URL, a stale bookmark, a
  truncated link) threw NumberFormatException out of serve(). NanoHTTPD's
  ClientHandler swallows it (logs, closes the socket), so the browser got an
  aborted/empty response instead of the requested logbook view.
- pageIndex was clamped with `if (pageIndex > pageCount) pageIndex = pageCount`
  but never floored, so ?page=0 (or a negative page) left pageIndex < 1 and the
  paged query's `LIMIT (pageIndex - 1) * pageSize, pageSize` offset went
  negative — a wrong/empty result set numbered from a negative "No." column.

Route every query-param parse through a new pure static parseQueryInt(value,
fallback) (falls back on null/non-numeric) and every page-index clamp through
clampPageIndex(pageIndex, pageCount) (floors to 1; pageCount is always >= 1).
Applied at all three paged views (getMessages/getSWLQsoMessages/getQsoLogs) and
both import-session handlers. Behaviour is byte-identical for well-formed
requests; malformed ones fall back to a sane default instead of breaking.

Both helpers are pure and covered by LogHttpServerQueryParamTest (pure JVM,
mirrors LogHttpServerPageCountTest); the guard cases fail against the pre-fix
behaviour. Verified :app:testDebugUnitTest + :app:assembleDebug (4 ABIs) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the always-on LAN-reachable LogHttpServer web-logbook endpoints against malformed pagination/import-session query parameters so that bad ?page=, ?pageSize=, and ?session= values no longer abort responses or produce negative SQL offsets.

Changes:

  • Added parseQueryInt(...) (safe int parsing with fallback) and clampPageIndex(...) (floor/cap for 1-based page indices) to LogHttpServer.
  • Routed pagination and import-session parsing through these helpers and clamped page indices for the three paged list views.
  • Added pure-JVM unit tests covering malformed/whitespace parsing and page-index clamping invariants.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
ft8af/app/src/main/java/com/k1af/ft8af/html/LogHttpServer.java Introduces safe query-int parsing and page-index clamping, and applies them to paged views and import-session handlers to prevent aborted responses and negative offsets.
ft8af/app/src/test/java/com/k1af/ft8af/html/LogHttpServerQueryParamTest.java Adds JVM unit tests to lock in the new guarded behavior for query parsing and page-index clamping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@patrickrb
patrickrb merged commit 880e803 into dev Jul 15, 2026
16 checks passed
@patrickrb
patrickrb deleted the optio/task-a3f984a8-9bc8-4c43-80e3-b337c3984007 branch July 15, 2026 13:10
@patrickrb patrickrb mentioned this pull request Jul 20, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants