Skip to content

Fix: Skip empty lines when listening to OAuth callback - #1706

Open
spacid wants to merge 1 commit into
librespot-org:devfrom
spacid:fix/skip-empty-lines-oauth-callback
Open

Fix: Skip empty lines when listening to OAuth callback#1706
spacid wants to merge 1 commit into
librespot-org:devfrom
spacid:fix/skip-empty-lines-oauth-callback

Conversation

@spacid

@spacid spacid commented Apr 19, 2026

Copy link
Copy Markdown

It is possible that a browser, after login, sends out empty lines through the socket before sending the auth code. This fix will skip the empty lines and will parse the first non-empty line.

This fixes 1705.

I am not sure if this fix is significant enough to be worth mentioning in CHANGELOG.md, so I omitted it.

It is possible that a browser, after login, sends out empty lines through
the socket before sending the auth code. This fix will skip the empty lines
and will parse the first non-empty line.
@hunterjsb

Copy link
Copy Markdown

cargo clippy failure is unrelated — pre-existing lint in metadata/src/audio/item.rs:197, already fixed on main (sort_by_key). This diff is clippy-clean. Should go green on rebase/CI re-run. Fix is confirmed working by multiple users in #1705 — worth merging.

LargeModGames added a commit to LargeModGames/spotatui-librespot that referenced this pull request Aug 6, 2026
Builds on the cherry-picked librespot-org#1706. That fix moves to the next connection
when a request line is blank, which is right only if the blank line and the
redirect arrive on separate connections. A leading CRLF on the same
connection is what RFC 9112 section 2.2 tells servers to tolerate, and
skipping the connection there discards the code and then blocks forever in
accept(). Read past blank lines within a connection instead, and fall
through to the next connection only when this one produces no request line
at all, so both shapes work.

Also stop treating a parseable but non-callback request as fatal. A
GET /favicon.ico still returned Err from get_code, dropped the listener and
closed the port, so the redirect carrying the code hit a dead port and the
user was stranded on a browser "unable to connect" page. Answer those with a
400 and keep waiting. A per-connection read timeout stops a socket that is
opened but never written from parking the login, and bounded skip counts
keep a misbehaving client from holding the server open indefinitely.

accept_authcode is split out so tests can bind port 0 instead of racing for
a fixed port. Covers both librespot#1705 shapes plus the favicon case.
LargeModGames added a commit to LargeModGames/spotatui that referenced this pull request Aug 6, 2026
)

# Summary

Fixes #414. The streaming login hands port 8989 to librespot, whose
callback server accepted exactly one connection and gave up if it was
not the redirect, dropping the listener and closing the port. Some
browsers, LibreWolf in particular, send a bare CRLF or open a connection
without writing to it before the real callback arrives. librespot
consumed that, failed to parse it, and closed the port, so the redirect
carrying the code hit a dead port. The user is left on a browser "unable
to connect" page, and because the flow errors before `save_credentials`
it repeats on every launch, which is the "streaming cookie does not get
cached" the reporter described.

Not Windows specific. Three independent reports line up: upstream
[librespot#1705](librespot-org/librespot#1705)
(LibreWolf + ncspot on Fedora, with a debug trace showing the empty line
and vanilla Firefox working as a control), #234 (LibreWolf on Gentoo,
same "web API login works, streaming fails" split), and #364 (a
commenter with `AuthCodeListenerParse` in their log who confirmed Chrome
works). The common variable is the browser.

spotatui's own callback server never had this problem:
`extract_callback_url` runs `split_whitespace()` over the whole buffer,
which steps over a leading CRLF, and it answers unrelated requests with
a 400 and keeps waiting. `read_line` stops at the first `\n`. Only the
librespot half was fragile, which is why the web API login succeeded and
the streaming one did not.

Fixed in our librespot fork rather than here, since that is where the
defective listener lives. The fork cherry-picks upstream PR
[#1706](librespot-org/librespot#1706) with its
original attribution, then hardens it: #1706 skips to the next
*connection* on a blank line, which is correct only if the blank line
arrives on its own connection and hangs if it is a leading CRLF on the
same one. This PR bumps the `[patch.crates-io]` rev to pick that up.

Three unrelated startup bugs found while tracing this are fixed in their
own commits:

- **Auto-update deadlock.** `run_auto_update` ran concurrently with
authentication in a `tokio::join!` and re-exec'd immediately on a
successful install. The re-exec blocks the task in `Command::status()`,
so the joined authentication future stops being polled while it still
owns the callback port, and the child, which repeats startup from
scratch, cannot bind that port. The parent waits on the child, the child
waits on a port the parent will never release. The check still runs
concurrently; only the restart moves to after the join.
- **Log path.** `setup_logging` hard-coded `/tmp/spotatui_logs/`, which
on Windows is drive-relative, so the app printed a location the user's
shell could not resolve directly above the line inviting them to report
bugs. Now resolved through `std::env::temp_dir`.
- **OAuth port probe.** `wait_for_oauth_callback_port` refused to start
the login when its probe timed out, so we never even attempted and
reported something vaguer than the bind error librespot would have
produced. It now warns and proceeds.

# Testing

- `cargo fmt --all` (and `--check`, clean)
- `cargo clippy --no-default-features --features telemetry -- -D
warnings` (clean)
- `cargo clippy -- -D warnings` (clean, default features)
- `cargo test --no-default-features --features telemetry` (544 passed)
- `cargo test` (809 passed)

In the fork, `cargo test -p librespot-oauth` (14 passed) and `cargo
clippy -p librespot-oauth --all-targets -- -D warnings` (clean). The
listener tests cover both shapes of librespot#1705 (blank line on the
same connection, and on a separate one), a `/favicon.ico` request before
the callback, a code surviving a browser that hangs up before the
success page is written, and a peer dribbling bytes being cut off at the
deadline. They run behind a watchdog so a regression fails the suite
instead of hanging it.

Reviewed across five rounds with the Codex CLI, which caught four real
defects: a captured authorization code being discarded when the success
page failed to write, the accept loop having no overall deadline, HTTP
header lines being read as if each were a request, and the deadline not
covering an in-flight read.

# Additional notes

Not verified against a live LibreWolf on Windows. I am on Linux and
cannot reproduce the reporter's setup, so this rests on code reading
plus the three corroborating reports above rather than an observed
repro.

A fork fix reaches everyone who installs today: GitHub releases, winget,
Homebrew, both AUR packages, and `install.sh`/`install.ps1` all build
with `[patch]` active. The one gap is `cargo install spotatui` from
crates.io, which is stuck at 0.40.2 because the v0.40.3 publish job
fails to compile against upstream librespot (`unresolved import
librespot_connect::SavedPlaybackState`). Worth fixing separately; those
users cannot get 0.40.3+ by any route today.

Workaround for anyone hitting this before the next release: set
`enable_streaming: false` in `client.yml`. That stops the every-launch
browser flow and leaves Spotify Connect working.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Log files now use a platform-appropriate temporary directory and
include the process ID.
  - The changelog displays the actual log-file location.
- Automatic updates restart after authentication resources are released.

- **Bug Fixes**
- Improved OAuth callback handling when the callback port is
unavailable.
  - Streaming authentication errors now include more actionable details.
  - Updated playback and connection reliability fixes.

- **Documentation**
- Updated safe-by-default guidance to explain how to find the log-file
location.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@m00grin

m00grin commented Sep 2, 2026

Copy link
Copy Markdown

Verified this against current dev. Applies cleanly, fmt/clippy/test all
pass. Real bug, closes #1705, isolated fix with no conflicts in the queue
right now.

@kingosticks

kingosticks commented Sep 2, 2026

Copy link
Copy Markdown
Member

It needs a changelog entry. Or it could be closed because librewolf fixed their bug. Either would be OK by me.

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.

Logging in via LibreWolf results in parse error in the OAuth callback

4 participants