@@ -141,13 +141,34 @@ impl<T: str::FromStr> str::FromStr for QualityItem<T> {
141141 let mut quality = Quality :: MAX ;
142142
143143 if let Some ( ( start, end, value) ) = quality_parameter {
144- // reject q-values longer than allowed by the spec (3 d.p.)
145- if value. len ( ) > 5 {
146- return Err ( ParseError :: Header ) ;
147- }
144+ // Parse decimal digits directly to preserve the specified precision.
145+ quality = match value. as_bytes ( ) {
146+ [ b'0' ] => Quality :: ZERO ,
147+ [ b'1' ] => Quality :: MAX ,
148+
149+ [ b'0' , b'.' , digits @ ..]
150+ if digits. len ( ) <= 3 && digits. iter ( ) . all ( u8:: is_ascii_digit) =>
151+ {
152+ let mut value = 0 ;
153+ let mut place = 100 ;
154+
155+ for & digit in digits {
156+ value += u16:: from ( digit - b'0' ) * place;
157+ place /= 10 ;
158+ }
159+
160+ Quality ( value)
161+ }
162+
163+ [ b'1' , b'.' , digits @ ..]
164+ if digits. len ( ) <= 3 && digits. iter ( ) . all ( |& digit| digit == b'0' ) =>
165+ {
166+ Quality :: MAX
167+ }
168+
169+ _ => return Err ( ParseError :: Header ) ,
170+ } ;
148171
149- let value = value. parse :: < f32 > ( ) . map_err ( |_| ParseError :: Header ) ?;
150- quality = Quality :: try_from ( value) . map_err ( |_| ParseError :: Header ) ?;
151172 raw_item = if end == q_item_str. len ( ) {
152173 Cow :: Borrowed ( q_item_str[ ..start] . trim ( ) )
153174 } else {
@@ -405,6 +426,58 @@ mod tests {
405426 "gzip; q=2" . parse :: < QualityItem < Encoding > > ( ) . unwrap_err ( ) ;
406427 }
407428
429+ #[ test]
430+ fn quality_value_rejects_invalid_numeric_syntax ( ) {
431+ for value in [
432+ "" , ".5" , ".0001" , "+0.5" , "-0" , "-0.0" , "00.5" , "00001" , "1e-1" , "1e-4" , "0.0000" ,
433+ "1.0000" , "1.001" , "0.0.1" , "NaN" , "inf" , " 0.5" , "\" 0.5\" " ,
434+ ] {
435+ let input = format ! ( "gzip; q={value}" ) ;
436+
437+ assert ! (
438+ input. parse:: <QualityItem <Encoding >>( ) . is_err( ) ,
439+ "accepted invalid quality in {input:?}"
440+ ) ;
441+ }
442+ }
443+
444+ #[ test]
445+ fn quality_value_accepts_valid_numeric_syntax ( ) {
446+ for ( value, expected) in [
447+ ( "0" , Quality :: ZERO ) ,
448+ ( "0." , Quality :: ZERO ) ,
449+ ( "0.0" , Quality :: ZERO ) ,
450+ ( "0.00" , Quality :: ZERO ) ,
451+ ( "0.000" , Quality :: ZERO ) ,
452+ ( "0.001" , Quality :: MIN ) ,
453+ ( "0.01" , Quality ( 10 ) ) ,
454+ ( "0.1" , Quality ( 100 ) ) ,
455+ ( "0.10" , Quality ( 100 ) ) ,
456+ ( "0.100" , Quality ( 100 ) ) ,
457+ ( "0.999" , Quality ( 999 ) ) ,
458+ ( "1" , Quality :: MAX ) ,
459+ ( "1." , Quality :: MAX ) ,
460+ ( "1.0" , Quality :: MAX ) ,
461+ ( "1.00" , Quality :: MAX ) ,
462+ ( "1.000" , Quality :: MAX ) ,
463+ ] {
464+ let input = format ! ( "gzip; q={value}" ) ;
465+ let parsed = input. parse :: < QualityItem < Encoding > > ( ) . unwrap ( ) ;
466+
467+ assert_eq ! ( parsed. quality, expected, "{input}" ) ;
468+ }
469+ }
470+
471+ #[ test]
472+ fn quality_value_preserves_thousandths ( ) {
473+ for value in 0 ..1000 {
474+ let input = format ! ( "gzip; q=0.{value:03}" ) ;
475+ let parsed = input. parse :: < QualityItem < Encoding > > ( ) . unwrap ( ) ;
476+
477+ assert_eq ! ( parsed. quality, Quality ( value) , "{input}" ) ;
478+ }
479+ }
480+
408481 #[ test]
409482 fn test_quality_item_ordering ( ) {
410483 let x = "gzip; q=0.5" . parse :: < QualityItem < Encoding > > ( ) . unwrap ( ) ;
0 commit comments