fix(providers): treat unicode punctuation as image path terminators (#10106)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
This commit is contained in:
@@ -49,8 +49,7 @@ pub fn detect_image_path(text: &str) -> Option<Cow<'_, str>> {
|
||||
};
|
||||
|
||||
let terminator = text.get(end..).and_then(|rest| rest.chars().next());
|
||||
let terminated =
|
||||
terminator.is_none_or(|c| c == '/' || c.is_whitespace() || c == '"' || c == '\'');
|
||||
let terminated = terminator.is_none_or(is_path_terminator);
|
||||
|
||||
if terminated {
|
||||
let mut floor = end.saturating_sub(MAX_PATH_LEN);
|
||||
@@ -63,7 +62,7 @@ pub fn detect_image_path(text: &str) -> Option<Cow<'_, str>> {
|
||||
let preceded_by_boundary = text
|
||||
.get(..start)
|
||||
.and_then(|prefix| prefix.chars().next_back())
|
||||
.is_none_or(|c| c.is_whitespace() || c == '"' || c == '\'');
|
||||
.is_none_or(is_path_leading_boundary);
|
||||
if !preceded_by_boundary {
|
||||
continue;
|
||||
}
|
||||
@@ -140,6 +139,30 @@ fn is_existing_image_path(candidate: &str) -> bool {
|
||||
path.is_absolute() && path.is_file() && is_image_file(path)
|
||||
}
|
||||
|
||||
fn is_path_leading_boundary(c: char) -> bool {
|
||||
c.is_whitespace()
|
||||
|| matches!(
|
||||
c,
|
||||
'"' | '\'' | '\u{00AB}' | '\u{00BB}' | '\u{2018}'
|
||||
..='\u{201F}' | '\u{2039}' | '\u{203A}'
|
||||
)
|
||||
}
|
||||
|
||||
fn is_path_terminator(c: char) -> bool {
|
||||
c == '/'
|
||||
|| c.is_whitespace()
|
||||
|| matches!(
|
||||
c,
|
||||
'"' | '\'' | '\u{00AB}' | '\u{00BB}' | '\u{2013}'
|
||||
..='\u{201F}' | '\u{2026}' | '\u{2039}' | '\u{203A}'
|
||||
)
|
||||
|| ('\u{2300}'..='\u{23FF}').contains(&c)
|
||||
|| ('\u{2600}'..='\u{27BF}').contains(&c)
|
||||
|| ('\u{2B00}'..='\u{2BFF}').contains(&c)
|
||||
|| ('\u{1F1E6}'..='\u{1F1FF}').contains(&c)
|
||||
|| ('\u{1F300}'..='\u{1FAFF}').contains(&c)
|
||||
}
|
||||
|
||||
/// Case-insensitive ASCII substring search returning a byte index into
|
||||
/// `haystack` (no allocation, so the index stays valid for slicing).
|
||||
fn find_ascii_ci(haystack: &str, needle: &str, from: usize) -> Option<usize> {
|
||||
@@ -286,6 +309,10 @@ mod tests {
|
||||
assert_eq!(detect_image_path(&text).as_deref(), Some(png_path_str));
|
||||
let text = format!("describe '{}'", png_path_str);
|
||||
assert_eq!(detect_image_path(&text).as_deref(), Some(png_path_str));
|
||||
let text = format!("describe “{}” please", png_path_str);
|
||||
assert_eq!(detect_image_path(&text).as_deref(), Some(png_path_str));
|
||||
let text = format!("describe «{}» please", png_path_str);
|
||||
assert_eq!(detect_image_path(&text).as_deref(), Some(png_path_str));
|
||||
|
||||
// A stray closing quote in prose must not act as a terminator for an
|
||||
// unquoted path.
|
||||
@@ -355,6 +382,53 @@ mod tests {
|
||||
assert_eq!(detect_image_path(&text).as_deref(), Some(png_path_str));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_detect_image_path_with_unicode_separators() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let png_data = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
|
||||
let png_path = temp_dir.path().join("photo.png");
|
||||
std::fs::write(&png_path, png_data).unwrap();
|
||||
let png_path_str = png_path.to_str().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
detect_image_path(&format!("{png_path_str}🙂")).as_deref(),
|
||||
Some(png_path_str)
|
||||
);
|
||||
assert_eq!(
|
||||
detect_image_path(&format!("{png_path_str}🇺🇸")).as_deref(),
|
||||
Some(png_path_str)
|
||||
);
|
||||
assert_eq!(
|
||||
detect_image_path(&format!("{png_path_str}⌚")).as_deref(),
|
||||
Some(png_path_str)
|
||||
);
|
||||
assert_eq!(
|
||||
detect_image_path(&format!("{png_path_str}⭐")).as_deref(),
|
||||
Some(png_path_str)
|
||||
);
|
||||
assert_eq!(
|
||||
detect_image_path(&format!("{png_path_str}… more text")).as_deref(),
|
||||
Some(png_path_str)
|
||||
);
|
||||
assert_eq!(
|
||||
detect_image_path(&format!("{png_path_str}\u{2014}more text")).as_deref(),
|
||||
Some(png_path_str)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
detect_image_path(&format!("{png_path_str}\u{200B}.backup")).as_deref(),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
detect_image_path(&format!("{png_path_str}\u{0301}")).as_deref(),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
detect_image_path(&format!("file:{png_path_str}")).as_deref(),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_detect_image_path_ignores_urls_and_longer_extensions() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user