diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-11-05 09:09:50 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-11-05 09:09:50 +0000 |
| commit | ca385bfac567697f050ccaa506f9fb27681750de (patch) | |
| tree | 55c6aa91cf7725f7a6a087ec52e54673e7cf9738 /tools | |
| parent | 32df466c5c851c1a3e88c973627a9c07d2634e2b (diff) | |
The car plays our copy too, and the transcript URL stops leaking the token
**Android Auto was the one caller still streaming from publishers.** `onAddMediaItems` resolved a
browse id straight to a media item without asking whether the audio existed, so an undownloaded
episode was handed to the player as its publisher's URL. That is the worst place for it: the car
is where most of this library is actually heard, and misplaced skips are least obvious there.
It goes through `PlaybackAdvancer.startFetchingIfNeeded` now -- start, and fetch first if there is
no file -- the same decision the in-app paths make, in the one class tested for it. A fetch that
fails stays failed rather than falling back: the publisher's copy carries different advertising,
so the server's measured breaks land in the wrong place, and silence that can be retried beats
skipping into the programme.
With that, nothing reached the fallback, so it is gone. `setUri(localPath ?: mediaUrl)` is now
`setUri(localPath)`, `MediaUrl` is deleted, and OutboundRequestsTest asserts zero remote media
items rather than pinning one. `start` also reports Gone for an episode whose show *and* file have
both gone, which is what made the fallback removable -- an orphan that still has its file plays.
**Two findings from the security review of the last commit, both mine, both real:**
- `Request.Builder().url()` throws on a URL it cannot parse and only IOException was caught, so a
feed with a junk enclosure threw straight past the Result its caller folds. Survivable when this
ran from a worker; not now that every play goes through a fetch, where one bad enclosure in one
feed would stop any episode starting. Caught in the downloader, and `fetchAndStart` turns a
thrown fetch into FetchFailed -- rethrowing CancellationException, which a test now pins,
because swallowing it would report a cancelled start as a failed one and leave a false error on
screen.
- `FeedFetcher` logged whole feed URLs. Private feeds carry a token in the query and some carry
credentials in the userinfo. `FeedFailureText.redact` cuts them to scheme and host, and is
tested as the security control it is rather than as tidiness. The unparsable branch logs neither
the address nor the exception, and says why: that exception's message *is* the address, so
logging the cause would put back exactly what redacting removes.
**The transcript URL no longer carries the token.** Apache logs the request line, so every
/api/transcript fetch wrote the server's token into its access log in plaintext, across fourteen
rotations. It was redundant there: the phone sends Authorization: Bearer anyway and tokenOK takes
either. The audio URL still carries one -- not redundant, the downloader has no credentials of its
own -- so removing that needs an app change installed before the server stops emitting it.
The gate refused this twice and was right both times. The coverage drop was not all deletion: the
redaction and the cancellation contract were genuinely untested, and have tests now. Then detekt
caught an import left behind by moving the redaction into FeedFailureText -- a move I made after
the last time I ran it.
1031 app tests, 376 server tests, detekt 0, lint clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'tools')
0 files changed, 0 insertions, 0 deletions
