fix(telegram): contain voice file extensions (#10456)
This commit is contained in:
@@ -256,19 +256,7 @@ impl TelegramGateway {
|
||||
std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o700))?;
|
||||
}
|
||||
|
||||
let ext = mime_type
|
||||
.and_then(|m| m.rsplit('/').next())
|
||||
.map(|sub| {
|
||||
// Normalise common MIME sub-types to file extensions.
|
||||
match sub {
|
||||
"mpeg" => "mp3",
|
||||
"mp4" | "x-m4a" => "m4a",
|
||||
"ogg" => "ogg",
|
||||
"wav" | "x-wav" => "wav",
|
||||
other => other,
|
||||
}
|
||||
})
|
||||
.unwrap_or("ogg");
|
||||
let ext = Self::voice_file_extension(mime_type);
|
||||
|
||||
let filename = format!("voice_{}.{ext}", uuid::Uuid::new_v4());
|
||||
let path = dir.join(filename);
|
||||
@@ -284,6 +272,36 @@ impl TelegramGateway {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn voice_file_extension(mime_type: Option<&str>) -> String {
|
||||
let media_type = mime_type
|
||||
.and_then(|mime| mime.split(';').next())
|
||||
.map(str::trim)
|
||||
.map(str::to_ascii_lowercase);
|
||||
let subtype = media_type
|
||||
.as_deref()
|
||||
.and_then(|mime| mime.strip_prefix("audio/"));
|
||||
|
||||
let Some(subtype) = subtype else {
|
||||
return "ogg".to_string();
|
||||
};
|
||||
|
||||
match subtype {
|
||||
"mpeg" => "mp3".to_string(),
|
||||
"mp4" | "x-m4a" => "m4a".to_string(),
|
||||
"ogg" => "ogg".to_string(),
|
||||
"wav" | "x-wav" | "vnd.wave" => "wav".to_string(),
|
||||
other
|
||||
if other.len() <= 16
|
||||
&& other.bytes().enumerate().all(|(index, byte)| {
|
||||
byte.is_ascii_alphanumeric() || (index > 0 && matches!(byte, b'-' | b'_'))
|
||||
}) =>
|
||||
{
|
||||
other.to_string()
|
||||
}
|
||||
_ => "ogg".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the text prompt that tells Goose about a voice message file.
|
||||
fn voice_prompt(
|
||||
path: &std::path::Path,
|
||||
@@ -791,6 +809,63 @@ mod tests {
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn voice_file_extension_preserves_safe_audio_formats() {
|
||||
let cases = [
|
||||
(Some("audio/mpeg"), "mp3"),
|
||||
(Some("audio/mp4"), "m4a"),
|
||||
(Some("audio/x-m4a"), "m4a"),
|
||||
(Some("audio/ogg; codecs=opus"), "ogg"),
|
||||
(Some("audio/x-wav"), "wav"),
|
||||
(Some("audio/vnd.wave"), "wav"),
|
||||
(Some("audio/flac"), "flac"),
|
||||
(Some("audio/WEBM"), "webm"),
|
||||
(Some("Audio/MPEG"), "mp3"),
|
||||
(Some("AUDIO/WEBM"), "webm"),
|
||||
];
|
||||
|
||||
for (mime_type, expected) in cases {
|
||||
assert_eq!(TelegramGateway::voice_file_extension(mime_type), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn voice_file_extension_rejects_filename_syntax() {
|
||||
let invalid = [
|
||||
None,
|
||||
Some("application/ogg"),
|
||||
Some("audio/..\\..\\outside"),
|
||||
Some("audio/../../outside"),
|
||||
Some("audio/ogg:stream"),
|
||||
Some("audio/ogg.stream"),
|
||||
Some("audio/ogg\nnext"),
|
||||
Some("audio/ogg; touch=outside"),
|
||||
Some("audio/ogg$HOME"),
|
||||
Some("audio/åudio"),
|
||||
Some("audio/this-subtype-is-too-long"),
|
||||
];
|
||||
|
||||
for mime_type in invalid {
|
||||
assert_eq!(TelegramGateway::voice_file_extension(mime_type), "ogg");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_voice_file_contains_untrusted_mime_before_pairing() {
|
||||
let bytes = b"unpaired voice data";
|
||||
let path = TelegramGateway::save_voice_file(bytes, Some("audio/..\\..\\outside")).unwrap();
|
||||
|
||||
let expected_dir = std::env::temp_dir().join("goose_voice");
|
||||
assert_eq!(path.parent(), Some(expected_dir.as_path()));
|
||||
let filename = path.file_name().unwrap().to_str().unwrap();
|
||||
assert!(filename.starts_with("voice_"));
|
||||
assert!(filename.ends_with(".ogg"));
|
||||
assert!(!filename.chars().any(|c| matches!(c, '/' | '\\' | ':')));
|
||||
assert_eq!(std::fs::read(&path).unwrap(), bytes);
|
||||
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cleanup_preserves_recent_files() {
|
||||
let dir = std::env::temp_dir().join("goose_voice");
|
||||
|
||||
Reference in New Issue
Block a user