diff --git a/src/api/auth.rs b/src/api/auth.rs index 89845c2..cf44946 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -36,7 +36,8 @@ fn validate_username(username: &str) -> Result<(), String> { #[cfg(feature = "server")] static EMAIL_REGEX: std::sync::LazyLock = std::sync::LazyLock::new(|| { - regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap() + regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") + .expect("EMAIL_REGEX 正则模式应在编译期通过校验") }); #[cfg(feature = "server")] diff --git a/src/api/comments/helpers.rs b/src/api/comments/helpers.rs index 2bfcc76..43df54b 100644 --- a/src/api/comments/helpers.rs +++ b/src/api/comments/helpers.rs @@ -93,7 +93,8 @@ pub fn validate_comment_name(name: &str) -> Result<(), String> { #[cfg(feature = "server")] static EMAIL_REGEX: std::sync::LazyLock = std::sync::LazyLock::new(|| { - regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap() + regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") + .expect("EMAIL_REGEX 正则模式应在编译期通过校验") }); /// 校验评论作者邮箱格式。 diff --git a/src/api/image.rs b/src/api/image.rs index 54a8cb4..3cecea8 100644 --- a/src/api/image.rs +++ b/src/api/image.rs @@ -252,6 +252,10 @@ fn image_response( headers: &HeaderMap, ) -> Response { let etag = etag_for(&data); + // etag 形如 `"deadbeef..."`(引号 + hex),都是合法 token-char,from_str 不可能失败。 + // 用 expect 说明恒成立的不变量,避免裸 unwrap 触发 lint。 + let etag_value = HeaderValue::from_str(&etag) + .expect("etag 仅含 ASCII hex 与双引号,必然是合法的 HeaderValue"); if let Some(if_none_match) = headers .get(header::IF_NONE_MATCH) @@ -261,7 +265,7 @@ fn image_response( return ( StatusCode::NOT_MODIFIED, [ - (header::ETAG, HeaderValue::from_str(&etag).unwrap()), + (header::ETAG, etag_value.clone()), ( header::CACHE_CONTROL, HeaderValue::from_static(cache_control), @@ -286,7 +290,7 @@ fn image_response( header::CACHE_CONTROL, HeaderValue::from_static(cache_control), ), - (header::ETAG, HeaderValue::from_str(&etag).unwrap()), + (header::ETAG, etag_value), ( header::X_CONTENT_TYPE_OPTIONS, HeaderValue::from_static("nosniff"), diff --git a/src/api/markdown.rs b/src/api/markdown.rs index 9f7b7d6..50aba54 100644 --- a/src/api/markdown.rs +++ b/src/api/markdown.rs @@ -423,7 +423,8 @@ where // 匹配 pulldown-cmark 产出的 ...... // pulldown-cmark 格式可控:src 在前,alt 在后,属性用双引号 static IMG_RE: LazyLock = LazyLock::new(|| { - Regex::new(r#""#).unwrap() + Regex::new(r#""#) + .expect("IMG_RE 正则模式应在编译期通过校验") }); IMG_RE @@ -476,8 +477,10 @@ fn wrap_tables(html: &str) -> String { use regex::Regex; use std::sync::LazyLock; - static TABLE_RE: LazyLock = - LazyLock::new(|| Regex::new(r"(?s)]*)?>.*?").unwrap()); + static TABLE_RE: LazyLock = LazyLock::new(|| { + Regex::new(r"(?s)]*)?>.*?") + .expect("TABLE_RE 正则模式应在编译期通过校验") + }); TABLE_RE .replace_all(html, |caps: ®ex::Captures| { diff --git a/src/api/rate_limit.rs b/src/api/rate_limit.rs index 4bba3de..d0b8da6 100644 --- a/src/api/rate_limit.rs +++ b/src/api/rate_limit.rs @@ -28,7 +28,8 @@ fn env_or(key: &str, default: u32) -> NonZeroU32 { .ok() .and_then(|s| s.parse::().ok()) .unwrap_or(default); - NonZeroU32::new(val.max(1)).unwrap() + // val.max(1) 保证 ≥ 1,NonZeroU32::new 必然 Some;expect 说明该不变量。 + NonZeroU32::new(val.max(1)).expect("val.max(1) 保证非零,NonZeroU32::new 不可能失败") } #[cfg(feature = "server")] @@ -82,7 +83,7 @@ static CODE_EXEC_LIMITER: LazyLock> = LazyLock:: static CODE_EXEC_DAILY_LIMITER: LazyLock> = LazyLock::new(|| { RateLimiter::keyed( Quota::with_period(Duration::from_secs(86_400)) - .unwrap() + .expect("with_period 仅在 Duration 为 0 时返回 None;86_400s 必然 Some") .allow_burst(env_or("RATE_LIMIT_CODE_EXEC_DAILY", 50)), ) }); diff --git a/src/components/post/post_content.rs b/src/components/post/post_content.rs index afc380d..c11a2b2 100644 --- a/src/components/post/post_content.rs +++ b/src/components/post/post_content.rs @@ -152,7 +152,8 @@ pub fn PostContent(content_html: String) -> Element { #[cfg(target_arch = "wasm32")] use_effect(move || { - let window = web_sys::window().unwrap(); + let window = + web_sys::window().expect("post_content use_effect 仅在 WASM 浏览器上下文执行:无 window"); // 调用 window.__initPostContent('.post-content'):函数不存在时静默跳过 // (与旧 eval 中的 if 守卫语义一致)。 diff --git a/src/middleware.rs b/src/middleware.rs index d327ff2..b48bc3b 100644 --- a/src/middleware.rs +++ b/src/middleware.rs @@ -222,7 +222,7 @@ pub(crate) async fn admin_guard( .status(StatusCode::FOUND) .header(header::LOCATION, "/login") .body(Body::empty()) - .unwrap() + .expect("静态 302 重定向响应(合法 status + 固定 header + 空 body)必然构造成功") } } diff --git a/src/theme.rs b/src/theme.rs index b871873..65cbe4c 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -368,7 +368,8 @@ pub fn ThemeToggle() -> Element { let coords = evt.client_coordinates(); let x = coords.x; let y = coords.y; - let window = web_sys::window().unwrap(); + let window = web_sys::window() + .expect("主题切换回调仅在 WASM 浏览器上下文执行:无 window"); let key = "__startThemeTransition".into(); if let Ok(fn_val) = js_sys::Reflect::get(&window, &key) { if !fn_val.is_undefined() && !fn_val.is_null() { diff --git a/src/utils/text.rs b/src/utils/text.rs index 8edbc8f..5cd35f7 100644 --- a/src/utils/text.rs +++ b/src/utils/text.rs @@ -5,27 +5,34 @@ use std::sync::LazyLock; /// 匹配 fenced code block(```...```)的正则。 -static CODE_BLOCK_RE: LazyLock = - LazyLock::new(|| regex::Regex::new(r"```[\s\S]*?```").unwrap()); +static CODE_BLOCK_RE: LazyLock = LazyLock::new(|| { + regex::Regex::new(r"```[\s\S]*?```").expect("CODE_BLOCK_RE 正则模式应在编译期通过校验") +}); /// 匹配行内代码(`...`)的正则。 -static INLINE_CODE_RE: LazyLock = - LazyLock::new(|| regex::Regex::new(r"`[^`]*`").unwrap()); +static INLINE_CODE_RE: LazyLock = LazyLock::new(|| { + regex::Regex::new(r"`[^`]*`").expect("INLINE_CODE_RE 正则模式应在编译期通过校验") +}); /// 匹配 Markdown 链接 `[text](url)` 的正则。 -static LINK_RE: LazyLock = - LazyLock::new(|| regex::Regex::new(r"\[([^\]]*)\]\([^)]*\)").unwrap()); +static LINK_RE: LazyLock = LazyLock::new(|| { + regex::Regex::new(r"\[([^\]]*)\]\([^)]*\)").expect("LINK_RE 正则模式应在编译期通过校验") +}); /// 匹配 Markdown 标题(# 到 ######)的正则。 -static HEADING_RE: LazyLock = - LazyLock::new(|| regex::Regex::new(r"^#{1,6}\s*").unwrap()); +static HEADING_RE: LazyLock = LazyLock::new(|| { + regex::Regex::new(r"^#{1,6}\s*").expect("HEADING_RE 正则模式应在编译期通过校验") +}); /// 匹配 Markdown 图片 `![alt](url)` 的正则。 -static IMAGE_RE: LazyLock = - LazyLock::new(|| regex::Regex::new(r"!\[([^\]]*)\]\([^)]*\)").unwrap()); +static IMAGE_RE: LazyLock = LazyLock::new(|| { + regex::Regex::new(r"!\[([^\]]*)\]\([^)]*\)").expect("IMAGE_RE 正则模式应在编译期通过校验") +}); /// 匹配任意空白字符的正则,用于把多个空白合并为单个空格。 -static WHITESPACE_RE: LazyLock = LazyLock::new(|| regex::Regex::new(r"\s+").unwrap()); +static WHITESPACE_RE: LazyLock = LazyLock::new(|| { + regex::Regex::new(r"\s+").expect("WHITESPACE_RE 正则模式应在编译期通过校验") +}); /// 去除 Markdown 标记,返回近似纯文本。 /// diff --git a/src/utils/time.rs b/src/utils/time.rs index be51c9a..82ad481 100644 --- a/src/utils/time.rs +++ b/src/utils/time.rs @@ -13,18 +13,23 @@ use chrono::DateTime; /// /// WASM 端用 `js_sys::Promise` + `web_sys::Window::set_timeout_*` 构造, /// 避免 `js_sys::eval` 字符串求值。全项目统一的 sleep 入口。 +/// +/// `setTimeout` 的 delay 参数是 i32,超过 `i32::MAX` 会被浏览器立即触发;这里 clamp +/// 到安全上限,既避免 `u32 -> i32` 转换溢出 panic,也贴合 setTimeout 的合法范围。 #[cfg(target_arch = "wasm32")] pub async fn sleep_ms(ms: u32) { use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; let promise = js_sys::Promise::new(&mut |resolve, _| { - web_sys::window() - .expect("no window") + // ms 是 u32,setTimeout 接受 i32;clamp 到 i32::MAX(约 24.8 天)避免溢出。 + let delay = ms.min(i32::MAX as u32) as i32; + let window = web_sys::window().expect("sleep_ms 必须在浏览器上下文中调用:无 window"); + window .set_timeout_with_callback_and_timeout_and_arguments_0( &resolve.unchecked_into(), - ms.try_into().unwrap(), + delay, ) - .expect("set_timeout failed"); + .expect("setTimeout with a number delay cannot fail per WebIDL"); }); let _ = JsFuture::from(promise).await; }