Guard web-logger uriList[2] reads so a missing month segment can't crash the handler#584
Open
patrickrb wants to merge 1 commit into
Open
Guard web-logger uriList[2] reads so a missing month segment can't crash the handler#584patrickrb wants to merge 1 commit into
patrickrb wants to merge 1 commit into
Conversation
…ment can't crash the handler The always-on web-logbook HTTP server (LogHttpServer.serve, started at app launch) read the trailing path segment uriList[2] in three dispatch branches without the `uriList.length >= 3` guard its sibling branches (DELFOLLOW/DELQSL/DELQSLCALLSIGN) already use: - SHOWQSL (`showQSLByMonth(uriList[2])`) sat OUTSIDE the serve try/catch, so a bare `GET /SHOWQSL` — which splits to `["", "SHOWQSL"]` (length 2) — threw an uncaught ArrayIndexOutOfBoundsException out of serve(). - DOWNQSL / DOWNQSLNOQSL built the Content-Disposition header from uriList[2] unconditionally even when the body branch had already fallen back to the default page for a missing segment, throwing inside the try and returning an opaque HTTP 500 instead of the intended default page. Route every uriList[2] read through a new bounds-checked static helper `uriSegment(uriList, index)` (returns null when the segment is absent), and the download filename through `downloadFileName(month)` (falls back to log.adi). Behaviour is unchanged for well-formed requests; malformed ones now return the default page. Both helpers are pure and covered by LogHttpServerUriSegmentTest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #584 +/- ##
============================================
+ Coverage 29.36% 29.41% +0.04%
Complexity 197 197
============================================
Files 179 179
Lines 24068 24097 +29
Branches 3263 3268 +5
============================================
+ Hits 7067 7087 +20
- Misses 16780 16787 +7
- Partials 221 223 +2
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR hardens the always-on LogHttpServer request dispatcher so malformed web-logbook URLs (missing a trailing month segment) don’t crash the handler or return HTTP 500s, and adds unit tests to pin the new guard behavior.
Changes:
- Added
LogHttpServer.uriSegment(...)to safely read URI path segments by index. - Added
LogHttpServer.downloadFileName(...)to generate a safe download filename when the month is missing. - Added a new pure-JVM unit test class validating segment bounds and filename fallback behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| ft8af/app/src/main/java/com/k1af/ft8af/html/LogHttpServer.java | Adds guarded URI segment access and uses it in SHOWQSL / DOWNQSL* paths to avoid ArrayIndexOutOfBoundsException. |
| ft8af/app/src/test/java/com/k1af/ft8af/html/LogHttpServerUriSegmentTest.java | Adds unit tests covering missing/out-of-range segment reads and download filename fallback. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+103
to
+105
| static String uriSegment(String[] uriList, int index) { | ||
| return uriList != null && index >= 0 && index < uriList.length ? uriList[index] : null; | ||
| } |
Comment on lines
194
to
203
| } else if (uri.equalsIgnoreCase("DOWNQSL")) { | ||
| if (uriList.length >= 3) { | ||
| msg = downQSLByMonth(uriList[2], true); | ||
| String month = uriSegment(uriList, 2); | ||
| if (month != null) { | ||
| msg = downQSLByMonth(month, true); | ||
| } else { | ||
| msg = HtmlContext.DEFAULT_HTML(); | ||
| } | ||
| response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain", msg); | ||
| response.addHeader("Content-Disposition", String.format("attachment;filename=log%s.adi", uriList[2])); | ||
| response.addHeader("Content-Disposition", "attachment;filename=" + downloadFileName(month)); | ||
|
|
Comment on lines
204
to
+212
| } else if (uri.equalsIgnoreCase("DOWNQSLNOQSL")) { | ||
| if (uriList.length >= 3) { | ||
| msg = downQSLByMonth(uriList[2], false); | ||
| String month = uriSegment(uriList, 2); | ||
| if (month != null) { | ||
| msg = downQSLByMonth(month, false); | ||
| } else { | ||
| msg = HtmlContext.DEFAULT_HTML(); | ||
| } | ||
| response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain", msg); | ||
| response.addHeader("Content-Disposition", String.format("attachment;filename=log%s.adi", uriList[2])); | ||
| response.addHeader("Content-Disposition", "attachment;filename=" + downloadFileName(month)); |
Comment on lines
+49
to
+56
| @Test | ||
| public void outOfRangeAndDegenerateInputs_areNull() { | ||
| String[] one = {"", "SHOWQSL"}; | ||
| assertThat(LogHttpServer.uriSegment(one, 5)).isNull(); | ||
| assertThat(LogHttpServer.uriSegment(one, -1)).isNull(); | ||
| assertThat(LogHttpServer.uriSegment(new String[0], 0)).isNull(); | ||
| assertThat(LogHttpServer.uriSegment(null, 2)).isNull(); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
LogHttpServeris the always-on web-logbook HTTP server; it's constructed andstart()ed unconditionally duringMainViewModelinit (MainViewModel.java:936), so it listens onDEFAULT_PORTfor the whole app session and any browser on the same LAN (or the phone itself) can reach it.Its request dispatcher,
serve(), reads the trailing path segmenturiList[2](fromgetUri().split("/")) in three branches without theuriList.length >= 3guard that its sibling branches —DELFOLLOW,DELQSL,DELQSLCALLSIGN— already have:SHOWQSL—msg = HTML_STRING(showQSLByMonth(uriList[2]));sits outside theserve()try/catch. A bareGET /SHOWQSLsplits to["", "SHOWQSL"](length 2), souriList[2]throws an uncaughtArrayIndexOutOfBoundsExceptionstraight out ofserve().DOWNQSL/DOWNQSLNOQSL— the body branch correctly falls back to the default page when the segment is missing, but theContent-Dispositionheader line then dereferencesuriList[2]unconditionally. That throws inside thetry, so the client gets an opaque HTTP 500 instead of the intended default page.Fix
Route every
uriList[2]read through a new bounds-checked static helperuriSegment(uriList, index)(returnsnullwhen the segment is absent), and build the download filename throughdownloadFileName(month)(falls back tolog.adi). This mirrors the guard the sibling branches already use.GET /SHOWQSL/202401andGET /DOWNQSL/202401produce byte-identical output/headers (downloadFileName("202401") == "log202401.adi", exactly whatString.format("log%s.adi", uriList[2])produced).Testing
LogHttpServerUriSegmentTest(6 cases) pins the guard: reproduces the exact"/SHOWQSL".split("/")→ length-2 shape, assertsuriSegment(...) == nullfor the missing/out-of-range/negative/empty/null cases, and coversdownloadFileNamepresent/absent-month behaviour. Matches the existingLogHttpServerPageCountTeststyle (no Robolectric needed)../gradlew :app:testDebugUnitTest— full unit-test suite green (BUILD SUCCESSFUL).Risk
Low. Localized to
LogHttpServer.serve()dispatch; no protocol/DSP/audio behavior touched, no change for valid requests. Java-only (no Kotlin/ktlint impact).🤖 Generated with Claude Code