Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [main] Fixed `--volume-ctrl fixed` not disabling volume control
- [core] Fix default permissions on credentials file and warn user if file is world readable
- [core] Try all resolved addresses for the dealer connection instead of failing after the first one.
- [discovery] Return an HTTP error response instead of panicking on malformed discovery login requests

## [0.8.0] - 2025-11-10

Expand Down
42 changes: 30 additions & 12 deletions discovery/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use aes::cipher::{KeyIvInit, StreamCipher};
use base64::engine::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64;
use bytes::Bytes;
use futures_util::{FutureExt, TryFutureExt};
use hmac::{Hmac, Mac};
use http_body_util::{BodyExt, Full};
use hyper::{Method, Request, Response, StatusCode, body::Incoming};
Expand All @@ -24,7 +23,7 @@ use super::{DiscoveryError, DiscoveryEvent};

use crate::{
core::config::DeviceType,
core::{Error, authentication::Credentials, diffie_hellman::DhLocalKeys},
core::{Error, authentication::Credentials, diffie_hellman::DhLocalKeys, error::ErrorKind},
};

type Aes128Ctr = ctr::Ctr128BE<aes::Aes128>;
Expand Down Expand Up @@ -234,10 +233,28 @@ impl RequestHandler {
res
}

fn error_response(&self, err: &Error) -> Response<Full<Bytes>> {
let status = match err.kind {
ErrorKind::InvalidArgument | ErrorKind::FailedPrecondition => StatusCode::BAD_REQUEST,
_ => StatusCode::SERVICE_UNAVAILABLE,
};

let body = json!({
"status": 102,
"spotifyError": 0,
"statusString": status.canonical_reason().unwrap_or("ERROR"),
})
.to_string();

let mut res = Response::new(Full::new(Bytes::from(body)));
*res.status_mut() = status;
res
}

async fn handle(
self: Arc<Self>,
request: Request<Incoming>,
) -> Result<hyper::Result<Response<Full<Bytes>>>, Error> {
) -> hyper::Result<Response<Full<Bytes>>> {
let mut params = Params::new();

let (parts, body) = request.into_parts();
Expand All @@ -257,11 +274,17 @@ impl RequestHandler {

let action = params.get("action").map(Cow::as_ref);

Ok(Ok(match (parts.method, action) {
Ok(match (parts.method, action) {
(Method::GET, Some("getInfo")) => self.handle_get_info(),
(Method::POST, Some("addUser")) => self.handle_add_user(&params)?,
(Method::POST, Some("addUser")) => match self.handle_add_user(&params) {
Ok(response) => response,
Err(err) => {
error!("could not handle discovery request: {err}");
self.error_response(&err)
}
},
_ => self.not_found(),
}))
})
}
}

Expand Down Expand Up @@ -325,12 +348,7 @@ impl DiscoveryServer {
let discovery = discovery.clone();

let svc = hyper::service::service_fn(move |request| {
discovery
.clone()
.handle(request)
.inspect_err(|e| error!("could not handle discovery request: {e}"))
.and_then(|x| async move { Ok(x) })
.map(Result::unwrap) // guaranteed by `and_then` above
discovery.clone().handle(request)
});

let conn = server.serve_connection(io, svc);
Expand Down
Loading