@@ -36,6 +36,7 @@ use crate::args::BaseArgs;
3636use crate :: auth:: { self , login} ;
3737use crate :: http:: ApiClient ;
3838use crate :: ui:: { fuzzy_select, is_interactive, with_spinner} ;
39+ use crate :: utils:: parse_duration_to_seconds;
3940
4041const MAX_TRACE_SPANS : usize = 5000 ;
4142const MAX_BTQL_PAGE_LIMIT : usize = 1000 ;
@@ -5039,16 +5040,20 @@ fn parse_duration_to_seconds(input: &str) -> Result<u64> {
50395040 return Ok ( seconds) ;
50405041 }
50415042
5042- let ( num_str, unit) = trimmed. split_at ( trimmed. len ( ) . saturating_sub ( 1 ) ) ;
5043+ let suffix = trimmed. chars ( ) . last ( ) . filter ( |ch| ch. is_ascii_alphabetic ( ) ) ;
5044+ let ( num_str, unit) = match suffix {
5045+ Some ( unit) => ( & trimmed[ ..trimmed. len ( ) - unit. len_utf8 ( ) ] , unit) ,
5046+ None => ( trimmed, 's' ) ,
5047+ } ;
50435048 let value: u64 = num_str
50445049 . trim ( )
50455050 . parse ( )
50465051 . with_context ( || format ! ( "invalid duration '{input}'" ) ) ?;
5047- let multiplier = match unit. to_ascii_lowercase ( ) . as_str ( ) {
5048- "s" => 1 ,
5049- "m" => 60 ,
5050- "h" => 60 * 60 ,
5051- "d" => 60 * 60 * 24 ,
5052+ let multiplier = match unit. to_ascii_lowercase ( ) {
5053+ 's' => 1 ,
5054+ 'm' => 60 ,
5055+ 'h' => 60 * 60 ,
5056+ 'd' => 60 * 60 * 24 ,
50525057 _ => bail ! ( "invalid duration '{input}'. expected suffix s/m/h/d" ) ,
50535058 } ;
50545059 Ok ( value. saturating_mul ( multiplier) )
@@ -6187,6 +6192,14 @@ mod tests {
61876192 assert_eq ! ( parse_duration_to_seconds( "1d" ) . expect( "days" ) , 86_400 ) ;
61886193 }
61896194
6195+ #[ test]
6196+ fn parse_duration_to_seconds_rejects_non_ascii_suffix_without_panicking ( ) {
6197+ for input in [ "1–" , "1é" , "1🙂" ] {
6198+ let err = parse_duration_to_seconds ( input) . expect_err ( "invalid unicode suffix" ) ;
6199+ assert ! ( err. to_string( ) . contains( "invalid duration" ) ) ;
6200+ }
6201+ }
6202+
61906203 #[ test]
61916204 fn build_base_filter_clause_uses_window_or_since ( ) {
61926205 let from_window = build_base_filter_clause ( None , "1h" , Some ( "metadata.model IS NOT NULL" ) )
0 commit comments