mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 08:01:26 +00:00
fix: truncate strings on char boundaries to avoid panics on multibyte input (#10390)
* fix: truncate strings on char boundaries to avoid panics on multibyte input * fix: add borrowed truncate_chars helper and pin ee ref for audit fix * docs: clarify truncate_with_ellipsis length contract * chore: update ee-repo-ref to bbfb0de0dc9fa06130a231eb10c64f60238d1bbd This commit updates the EE repository reference after PR #690 was merged in windmill-ee-private. Previous ee-repo-ref: de15aeff12daf457711f5b981de691418484535c New ee-repo-ref: bbfb0de0dc9fa06130a231eb10c64f60238d1bbd Automated by sync-ee-ref workflow. --------- Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1 +1 @@
|
||||
f78df23339e3136e8b6e9148a509508633448dd2
|
||||
bbfb0de0dc9fa06130a231eb10c64f60238d1bbd
|
||||
|
||||
@@ -422,11 +422,7 @@ async fn sign_expression(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
// Truncate expression for resource field if too long (max 255 chars)
|
||||
let resource = if request.expression.len() > 200 {
|
||||
format!("{}...", &request.expression[..200])
|
||||
} else {
|
||||
request.expression.clone()
|
||||
};
|
||||
let resource = windmill_common::utils::truncate_with_ellipsis(&request.expression, 200);
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
|
||||
@@ -5,6 +5,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::db::DB;
|
||||
use crate::error::Result;
|
||||
use crate::utils::truncate_with_ellipsis;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
|
||||
#[sqlx(type_name = "MESSAGE_TYPE", rename_all = "lowercase")]
|
||||
@@ -51,12 +52,8 @@ pub async fn get_or_create_conversation_with_id(
|
||||
return Ok(existing);
|
||||
}
|
||||
|
||||
// Truncate title to 25 char characters max
|
||||
let title = if title.len() > 25 {
|
||||
format!("{}...", &title[..25])
|
||||
} else {
|
||||
title.to_string()
|
||||
};
|
||||
// Truncate title to 25 characters max
|
||||
let title = truncate_with_ellipsis(title, 25);
|
||||
|
||||
// Create new conversation with provided ID
|
||||
let conversation = sqlx::query_as!(
|
||||
|
||||
@@ -1077,12 +1077,7 @@ pub fn format_setting_value(key: &str, value: &serde_json::Value) -> String {
|
||||
};
|
||||
}
|
||||
let value = mask_nested_sensitive(key, value);
|
||||
let s = value.to_string();
|
||||
if s.len() > 200 {
|
||||
format!("{}...", &s[..197])
|
||||
} else {
|
||||
s
|
||||
}
|
||||
crate::utils::truncate_with_ellipsis(&value.to_string(), 197)
|
||||
}
|
||||
|
||||
/// Extract the expiry timestamp from a license key JSON value.
|
||||
|
||||
@@ -1364,10 +1364,45 @@ pub fn strip_json_nul(serialized: &str) -> Cow<'_, str> {
|
||||
Cow::Owned(String::from_utf8(out).expect("removing a NUL escape preserves valid UTF-8"))
|
||||
}
|
||||
|
||||
/// Prefix of `s` holding at most `max_chars` characters.
|
||||
/// Slicing by byte index (`&s[..n]`) panics when `n` lands inside a multibyte character.
|
||||
pub fn truncate_chars(s: &str, max_chars: usize) -> &str {
|
||||
match s.char_indices().nth(max_chars) {
|
||||
Some((byte_idx, _)) => &s[..byte_idx],
|
||||
None => s,
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep at most `max_chars` characters of `s`, appending `...` when anything was dropped —
|
||||
/// so a truncated result is `max_chars + 3` characters long, not `max_chars`.
|
||||
pub fn truncate_with_ellipsis(s: &str, max_chars: usize) -> String {
|
||||
let truncated = truncate_chars(s, max_chars);
|
||||
if truncated.len() < s.len() {
|
||||
format!("{}...", truncated)
|
||||
} else {
|
||||
s.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn truncate_handles_multibyte_at_boundary() {
|
||||
// Byte 25 of this string falls inside a 2-byte 'а'; naive `&s[..25]` would panic.
|
||||
let cyrillic = "а".repeat(30);
|
||||
assert_eq!(truncate_chars(&cyrillic, 25), "а".repeat(25));
|
||||
assert_eq!(
|
||||
truncate_with_ellipsis(&cyrillic, 25),
|
||||
format!("{}...", "а".repeat(25))
|
||||
);
|
||||
assert_eq!(truncate_with_ellipsis("ааааааааааааа", 25), "ааааааааааааа");
|
||||
assert_eq!(truncate_chars("abcd", 3), "abc");
|
||||
assert_eq!(truncate_with_ellipsis("abc", 3), "abc");
|
||||
assert_eq!(truncate_with_ellipsis("abcd", 3), "abc...");
|
||||
}
|
||||
|
||||
// The 6-char JSON escape for U+0000: backslash + "u0000". Written via an
|
||||
// escaped backslash so no literal NUL byte ever appears in this source.
|
||||
const NUL_ESC: &str = "\\u0000";
|
||||
|
||||
Reference in New Issue
Block a user