Skip to content
Merged
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
8 changes: 5 additions & 3 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ impl MisskeyClient {
if let Some(ref base) = self.base_url {
return format!("{base}/api/{endpoint}");
}
format!("https://{host}/api/{endpoint}")
let scheme = crate::insecure::http_scheme(host);
format!("{scheme}://{host}/api/{endpoint}")
}

/// Read the response body with a streaming size limit to prevent DoS.
Expand Down Expand Up @@ -2126,7 +2127,8 @@ impl MisskeyClient {
/// Fetch nodeinfo via .well-known/nodeinfo.
/// Returns the parsed nodeinfo JSON.
pub async fn fetch_nodeinfo(&self, host: &str) -> Result<Value, NoteDeckError> {
let well_known_url = format!("https://{host}/.well-known/nodeinfo");
let scheme = crate::insecure::http_scheme(host);
let well_known_url = format!("{scheme}://{host}/.well-known/nodeinfo");
let res = self
.client
.get(&well_known_url)
Expand Down Expand Up @@ -2162,7 +2164,7 @@ impl MisskeyClient {
})?;

// Validate URL to prevent SSRF: must be https://{host}/...
let expected_prefix = format!("https://{host}/");
let expected_prefix = format!("{scheme}://{host}/");
if !nodeinfo_url.starts_with(&expected_prefix) {
return Err(NoteDeckError::Api {
endpoint: ".well-known/nodeinfo".to_string(),
Expand Down
3 changes: 2 additions & 1 deletion src/commands/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ pub async fn run_login(
"write:votes",
];
let permission_str = permissions.join(",");
let scheme = crate::insecure::http_scheme(host);
let auth_url = format!(
"https://{host}/miauth/{session_id}?name=notecli&permission={permission_str}"
"{scheme}://{host}/miauth/{session_id}?name=notecli&permission={permission_str}"
);

match fmt {
Expand Down
59 changes: 59 additions & 0 deletions src/insecure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//! E2E テスト用の insecure host 判定 (notedeck #702)。
//!
//! 環境変数 `NOTECLI_INSECURE_HOSTS` (カンマ区切り・完全一致) に列挙された
//! ホストへは https/wss の代わりに http/ws で接続する。ローカルのモック
//! Misskey サーバー (`127.0.0.1:PORT`) に接続するための仕組みで、
//! デバッグビルド限定 (リリースビルドでは常に secure)。

pub(crate) fn is_insecure_host(host: &str) -> bool {
#[cfg(debug_assertions)]
{
std::env::var("NOTECLI_INSECURE_HOSTS")
.map(|v| v.split(',').any(|h| h.trim().eq_ignore_ascii_case(host)))
.unwrap_or(false)
}
#[cfg(not(debug_assertions))]
{
let _ = host;
false
}
}

pub(crate) fn http_scheme(host: &str) -> &'static str {
if is_insecure_host(host) {
"http"
} else {
"https"
}
}

pub(crate) fn ws_scheme(host: &str) -> &'static str {
if is_insecure_host(host) {
"ws"
} else {
"wss"
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn insecure_only_for_exact_listed_host() {
// 他テストと衝突しない値を使う (env はプロセス全体で共有されるため)
unsafe { std::env::set_var("NOTECLI_INSECURE_HOSTS", "127.0.0.1:39821, mock.test:8080") };
assert_eq!(http_scheme("127.0.0.1:39821"), "http");
assert_eq!(ws_scheme("mock.test:8080"), "ws");
// 完全一致のみ — 列挙外・部分一致は secure のまま
assert_eq!(http_scheme("127.0.0.1:39999"), "https");
assert_eq!(ws_scheme("127.0.0.1"), "wss");
unsafe { std::env::remove_var("NOTECLI_INSECURE_HOSTS") };
}

#[test]
fn secure_by_default() {
assert_eq!(http_scheme("misskey.io"), "https");
assert_eq!(ws_scheme("misskey.io"), "wss");
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod error;
pub mod event_bus;
pub mod format;
pub mod http_server;
pub(crate) mod insecure;
pub mod keychain;
pub mod models;
pub mod streaming;
Expand Down
3 changes: 2 additions & 1 deletion src/streaming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@ impl StreamingManager {
return Ok(());
}

let url = format!("wss://{host}/streaming?i={token}");
let scheme = crate::insecure::ws_scheme(&host);
let url = format!("{scheme}://{host}/streaming?i={token}");

// Attempt the initial connection (with timeout to prevent hang DoS).
// 失敗しても Err を返さず connection_task の再接続ループに委ねる:
Expand Down
Loading