mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): normalize loopback host for HTTP writes and stop Reminders nav flicker (#1086)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
co-authored by
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7
parent
770266d2a9
commit
771086fd69
@@ -53,16 +53,30 @@ pub fn relay_http_base_url(relay_url: &str) -> String {
|
||||
let trimmed = relay_url.trim().trim_end_matches('/');
|
||||
|
||||
if let Some(suffix) = trimmed.strip_prefix("wss://") {
|
||||
return format!("https://{suffix}");
|
||||
return format!("https://{}", normalize_loopback_host(suffix));
|
||||
}
|
||||
|
||||
if let Some(suffix) = trimmed.strip_prefix("ws://") {
|
||||
return format!("http://{suffix}");
|
||||
return format!("http://{}", normalize_loopback_host(suffix));
|
||||
}
|
||||
|
||||
trimmed.to_string()
|
||||
}
|
||||
|
||||
/// Rewrite an exact `localhost` host to `127.0.0.1`, preserving port and path.
|
||||
///
|
||||
/// macOS resolves `localhost` to both `::1` and `127.0.0.1`; reqwest's
|
||||
/// happy-eyeballs may try `::1` first, which fails when the relay binds IPv4
|
||||
/// only (`0.0.0.0`). Forcing the IPv4 literal makes every HTTP write connect on
|
||||
/// the first attempt. Exact-match only — `localhost.evil.com` is untouched.
|
||||
fn normalize_loopback_host(authority: &str) -> String {
|
||||
let host_len = authority.find([':', '/']).unwrap_or(authority.len());
|
||||
if &authority[..host_len] == "localhost" {
|
||||
return format!("127.0.0.1{}", &authority[host_len..]);
|
||||
}
|
||||
authority.to_string()
|
||||
}
|
||||
|
||||
pub fn relay_api_base_url() -> String {
|
||||
if let Some(base) = configured_env_var("BUZZ_RELAY_HTTP") {
|
||||
return base.trim_end_matches('/').to_string();
|
||||
@@ -539,9 +553,65 @@ pub async fn submit_event_with_keys(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{build_profile_event, classify_intercepted_response, parse_command_response};
|
||||
use super::{
|
||||
build_profile_event, classify_intercepted_response, parse_command_response,
|
||||
relay_http_base_url,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
// ── relay_http_base_url loopback normalization ───────────────────────────
|
||||
|
||||
#[test]
|
||||
fn loopback_ws_localhost_rewritten_to_ipv4() {
|
||||
assert_eq!(
|
||||
relay_http_base_url("ws://localhost:3000"),
|
||||
"http://127.0.0.1:3000"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopback_trailing_slash_rewritten_to_ipv4() {
|
||||
assert_eq!(
|
||||
relay_http_base_url("ws://localhost:3000/"),
|
||||
"http://127.0.0.1:3000"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_wss_host_unchanged() {
|
||||
assert_eq!(
|
||||
relay_http_base_url("wss://relay.example.com"),
|
||||
"https://relay.example.com"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopback_ipv4_literal_unchanged() {
|
||||
assert_eq!(
|
||||
relay_http_base_url("ws://127.0.0.1:3000"),
|
||||
"http://127.0.0.1:3000"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn localhost_substring_host_not_rewritten() {
|
||||
// Exact-match only: a host that merely starts with "localhost" must NOT
|
||||
// be rewritten, or a malicious host could hijack the loopback path.
|
||||
assert_eq!(
|
||||
relay_http_base_url("ws://localhost.evil.com:3000"),
|
||||
"http://localhost.evil.com:3000"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopback_wss_localhost_rewritten_to_ipv4() {
|
||||
// The wss:// dev arm normalizes identically to ws://.
|
||||
assert_eq!(
|
||||
relay_http_base_url("wss://localhost:3000"),
|
||||
"https://127.0.0.1:3000"
|
||||
);
|
||||
}
|
||||
|
||||
// ── classify_intercepted_response ────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { getIdentity } from "@/shared/api/tauri";
|
||||
import { useIdentityQuery } from "@/shared/api/hooks";
|
||||
import { RemindersPanel } from "./RemindersPanel";
|
||||
|
||||
export function RemindersScreen() {
|
||||
const identityQuery = useQuery({
|
||||
queryKey: ["identity"],
|
||||
queryFn: getIdentity,
|
||||
});
|
||||
const identityQuery = useIdentityQuery();
|
||||
|
||||
if (!identityQuery.data?.pubkey) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user