From 374f75658147932fe8f57d10ec7df41896a88418 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Thu, 17 Oct 2019 15:39:14 +0200 Subject: [PATCH 1/4] Re-add the Accept header --- src/common/accept.rs | 143 +++++++++++++++++++++ src/common/mod.rs | 4 +- src/disabled/accept.rs | 150 ----------------------- src/lib.rs | 3 +- src/util/mod.rs | 4 +- src/{disabled => }/util/quality_value.rs | 82 +++++++++---- 6 files changed, 205 insertions(+), 181 deletions(-) create mode 100644 src/common/accept.rs delete mode 100644 src/disabled/accept.rs rename src/{disabled => }/util/quality_value.rs (82%) diff --git a/src/common/accept.rs b/src/common/accept.rs new file mode 100644 index 00000000..b13504cb --- /dev/null +++ b/src/common/accept.rs @@ -0,0 +1,143 @@ +use http::HttpTryFrom; + +use mime::{self, Mime}; + +use util::QualityValue; + +fn qitem(mime: Mime) -> QualityValue { + QualityValue::new(mime, Default::default()) +} + +/// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) +/// +/// The `Accept` header field can be used by user agents to specify +/// response media types that are acceptable. Accept header fields can +/// be used to indicate that the request is specifically limited to a +/// small set of desired types, as in the case of a request for an +/// in-line image +/// +/// # ABNF +/// +/// ```text +/// Accept = #( media-range [ accept-params ] ) +/// +/// media-range = ( "*/*" +/// / ( type "/" "*" ) +/// / ( type "/" subtype ) +/// ) *( OWS ";" OWS parameter ) +/// accept-params = weight *( accept-ext ) +/// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] +/// ``` +/// +/// # Example values +/// * `audio/*; q=0.2, audio/basic` +/// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` +/// +/// # Examples +/// ``` +/// # extern crate headers; +/// extern crate mime; +/// extern crate http; +/// use headers::{Accept, QualityValue, HeaderMapExt}; +/// +/// let mut headers = http::HeaderMap::new(); +/// +/// headers.typed_insert( +/// Accept(vec![ +/// QualityValue::new(mime::TEXT_HTML, Default::default()), +/// ]) +/// ); +/// ``` +/// +/// ``` +/// # extern crate headers; +/// extern crate mime; +/// use headers::{Accept, QualityValue, HeaderMapExt}; +/// +/// let mut headers = http::HeaderMap::new(); +/// headers.typed_insert( +/// Accept(vec![ +/// QualityValue::new(mime::APPLICATION_JSON, Default::default()), +/// ]) +/// ); +/// ``` +/// ``` +/// # extern crate headers; +/// extern crate mime; +/// use headers::{Accept, QualityValue, HeaderMapExt}; +/// +/// let mut headers = http::HeaderMap::new(); +/// +/// headers.typed_insert( +/// Accept(vec![ +/// QualityValue::from(mime::TEXT_HTML), +/// QualityValue::from("application/xhtml+xml".parse::().unwrap()), +/// QualityValue::new( +/// mime::TEXT_XML, +/// 900.into() +/// ), +/// QualityValue::from("image/webp".parse::().unwrap()), +/// QualityValue::new( +/// mime::STAR_STAR, +/// 800.into() +/// ), +/// ]) +/// ); +/// ``` +#[derive(Debug)] +pub struct Accept(pub Vec>); + +impl ::Header for Accept { + fn name() -> &'static ::HeaderName { + &::http::header::ACCEPT + } + + fn decode<'i, I: Iterator>(values: &mut I) -> Result { + ::util::csv::from_comma_delimited(values).map(Accept) + } + + fn encode>(&self, values: &mut E) { + use std::fmt; + struct Format(F); + impl fmt::Display for Format + where + F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result, + { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + (self.0)(f) + } + } + let s = format!( + "{}", + Format( + |f: &mut fmt::Formatter<'_>| ::util::csv::fmt_comma_delimited( + &mut *f, + self.0.iter() + ) + ) + ); + values.extend(Some(::HeaderValue::try_from(s).unwrap())) + } +} + +impl Accept { + /// A constructor to easily create `Accept: */*`. + pub fn star() -> Accept { + Accept(vec![qitem(mime::STAR_STAR)]) + } + + /// A constructor to easily create `Accept: application/json`. + pub fn json() -> Accept { + Accept(vec![qitem(mime::APPLICATION_JSON)]) + } + + /// A constructor to easily create `Accept: text/*`. + pub fn text() -> Accept { + Accept(vec![qitem(mime::TEXT_STAR)]) + } + + /// A constructor to easily create `Accept: image/*`. + pub fn image() -> Accept { + Accept(vec![qitem(mime::IMAGE_STAR)]) + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 3a1e9c0f..789428c6 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -9,8 +9,8 @@ //pub use self::accept_charset::AcceptCharset; //pub use self::accept_encoding::AcceptEncoding; //pub use self::accept_language::AcceptLanguage; +pub use self::accept::Accept; pub use self::accept_ranges::AcceptRanges; -//pub use self::accept::Accept; pub use self::access_control_allow_credentials::AccessControlAllowCredentials; pub use self::access_control_allow_headers::AccessControlAllowHeaders; pub use self::access_control_allow_methods::AccessControlAllowMethods; @@ -125,7 +125,7 @@ macro_rules! bench_header { }; } -//mod accept; +mod accept; //mod accept_charset; //mod accept_encoding; //mod accept_language; diff --git a/src/disabled/accept.rs b/src/disabled/accept.rs deleted file mode 100644 index 3e8b7739..00000000 --- a/src/disabled/accept.rs +++ /dev/null @@ -1,150 +0,0 @@ -use mime::{self, Mime}; - -use {QualityItem, qitem}; - -header! { - /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) - /// - /// The `Accept` header field can be used by user agents to specify - /// response media types that are acceptable. Accept header fields can - /// be used to indicate that the request is specifically limited to a - /// small set of desired types, as in the case of a request for an - /// in-line image - /// - /// # ABNF - /// - /// ```text - /// Accept = #( media-range [ accept-params ] ) - /// - /// media-range = ( "*/*" - /// / ( type "/" "*" ) - /// / ( type "/" subtype ) - /// ) *( OWS ";" OWS parameter ) - /// accept-params = weight *( accept-ext ) - /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] - /// ``` - /// - /// # Example values - /// * `audio/*; q=0.2, audio/basic` - /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` - /// - /// # Examples - /// ``` - /// # extern crate headers; - /// extern crate mime; - /// use headers::{Headers, Accept, qitem}; - /// - /// let mut headers = Headers::new(); - /// - /// headers.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// ]) - /// ); - /// ``` - /// - /// ``` - /// # extern crate headers; - /// extern crate mime; - /// use headers::{Headers, Accept, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// Accept(vec![ - /// qitem(mime::APPLICATION_JSON), - /// ]) - /// ); - /// ``` - /// ``` - /// # extern crate headers; - /// extern crate mime; - /// use headers::{Headers, Accept, QualityItem, q, qitem}; - /// - /// let mut headers = Headers::new(); - /// - /// headers.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// qitem("application/xhtml+xml".parse().unwrap()), - /// QualityItem::new( - /// mime::TEXT_XML, - /// q(900) - /// ), - /// qitem("image/webp".parse().unwrap()), - /// QualityItem::new( - /// mime::STAR_STAR, - /// q(800) - /// ), - /// ]) - /// ); - /// ``` - (Accept, ACCEPT) => (QualityItem)+ - - test_accept { - // Tests from the RFC - test_header!( - test1, - vec![b"audio/*; q=0.2, audio/basic"], - Some(HeaderField(vec![ - QualityItem::new("audio/*".parse().unwrap(), q(200)), - qitem("audio/basic".parse().unwrap()), - ]))); - test_header!( - test2, - vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], - Some(HeaderField(vec![ - QualityItem::new(TEXT_PLAIN, q(500)), - qitem(TEXT_HTML), - QualityItem::new( - "text/x-dvi".parse().unwrap(), - q(800)), - qitem("text/x-c".parse().unwrap()), - ]))); - // Custom tests - test_header!( - test3, - vec![b"text/plain; charset=utf-8"], - Some(Accept(vec![ - qitem(TEXT_PLAIN_UTF_8), - ]))); - test_header!( - test4, - vec![b"text/plain; charset=utf-8; q=0.5"], - Some(Accept(vec![ - QualityItem::new(TEXT_PLAIN_UTF_8, - q(500)), - ]))); - - #[test] - fn test_fuzzing1() { - let raw: Raw = "chunk#;e".into(); - let header = Accept::parse_header(&raw); - assert!(header.is_ok()); - } - } -} - -impl Accept { - /// A constructor to easily create `Accept: */*`. - pub fn star() -> Accept { - Accept(vec![qitem(mime::STAR_STAR)]) - } - - /// A constructor to easily create `Accept: application/json`. - pub fn json() -> Accept { - Accept(vec![qitem(mime::APPLICATION_JSON)]) - } - - /// A constructor to easily create `Accept: text/*`. - pub fn text() -> Accept { - Accept(vec![qitem(mime::TEXT_STAR)]) - } - - /// A constructor to easily create `Accept: image/*`. - pub fn image() -> Accept { - Accept(vec![qitem(mime::IMAGE_STAR)]) - } -} - - -bench_header!(bench, Accept, { vec![b"text/plain; q=0.5, text/html".to_vec()] }); diff --git a/src/lib.rs b/src/lib.rs index d6bf2c9c..2b8cae85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,7 +78,7 @@ extern crate bitflags; extern crate bytes; extern crate headers_core; extern crate http; -extern crate mime; +pub extern crate mime; extern crate sha1; #[cfg(all(test, feature = "nightly"))] extern crate test; @@ -99,3 +99,4 @@ mod map_ext; pub use self::common::*; pub use self::map_ext::HeaderMapExt; +pub use self::util::QualityValue; diff --git a/src/util/mod.rs b/src/util/mod.rs index 7ac789d9..01c2104e 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -8,7 +8,7 @@ pub(crate) use self::fmt::fmt; pub(crate) use self::http_date::HttpDate; pub(crate) use self::iter::IterExt; //pub use language_tags::LanguageTag; -//pub use self::quality_value::{Quality, QualityValue}; +pub use self::quality_value::{Quality, QualityValue}; pub(crate) use self::seconds::Seconds; pub(crate) use self::value_string::HeaderValueString; @@ -20,7 +20,7 @@ mod flat_csv; mod fmt; mod http_date; mod iter; -//mod quality_value; +mod quality_value; mod seconds; mod value_string; diff --git a/src/disabled/util/quality_value.rs b/src/util/quality_value.rs similarity index 82% rename from src/disabled/util/quality_value.rs rename to src/util/quality_value.rs index bcc79728..499bae44 100644 --- a/src/disabled/util/quality_value.rs +++ b/src/util/quality_value.rs @@ -5,7 +5,6 @@ use std::default::Default; use std::fmt; use std::str; -#[cfg(test)] use self::internal::IntoQuality; /// Represents a quality used in quality values. @@ -36,18 +35,15 @@ impl Default for Quality { #[derive(Clone, PartialEq, Debug)] pub struct QualityValue { /// The actual contents of the field. - value: T, + pub value: T, /// The quality (client or server preference) for the value. - quality: Quality, + pub quality: Quality, } impl QualityValue { /// Creates a new `QualityValue` from an item and a quality. pub fn new(value: T, quality: Quality) -> QualityValue { - QualityValue { - value, - quality, - } + QualityValue { value, quality } } /* @@ -86,14 +82,14 @@ impl fmt::Display for QualityValue { match self.quality.0 { 1000 => Ok(()), 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')) + x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')), } } } impl str::FromStr for QualityValue { type Err = ::Error; - fn from_str(s: &str) -> ::Result> { + fn from_str(s: &str) -> Result, ::Error> { // Set defaults used if parsing fails. let mut raw_item = s; let mut quality = 1f32; @@ -113,22 +109,18 @@ impl str::FromStr for QualityValue { if 0f32 <= q_value && q_value <= 1f32 { quality = q_value; raw_item = parts[1]; - } else { - return Err(::Error::invalid()); - } - }, - Err(_) => { - return Err(::Error::invalid()) - }, + } else { + return Err(::Error::invalid()); + } + } + Err(_) => return Err(::Error::invalid()), } } } match raw_item.parse::() { // we already checked above that the quality is within range Ok(item) => Ok(QualityValue::new(item, from_f32(quality))), - Err(_) => { - Err(::Error::invalid()) - }, + Err(_) => Err(::Error::invalid()), } } } @@ -138,7 +130,10 @@ fn from_f32(f: f32) -> Quality { // this function is only used internally. A check that `f` is within range // should be done before calling this method. Just in case, this // debug_assert should catch if we were forgetful - debug_assert!(f >= 0f32 && f <= 1f32, "q value must be between 0.0 and 1.0"); + debug_assert!( + f >= 0f32 && f <= 1f32, + "q value must be between 0.0 and 1.0" + ); Quality((f * 1000f32) as u16) } @@ -147,6 +142,15 @@ fn q(val: T) -> Quality { val.into_quality() } +impl From for Quality +where + T: IntoQuality, +{ + fn from(x: T) -> Self { + x.into_quality() + } +} + mod internal { use super::Quality; @@ -163,7 +167,10 @@ mod internal { impl IntoQuality for f32 { fn into_quality(self) -> Quality { - assert!(self >= 0f32 && self <= 1f32, "float must be between 0.0 and 1.0"); + assert!( + self >= 0f32 && self <= 1f32, + "float must be between 0.0 and 1.0" + ); super::from_f32(self) } } @@ -175,7 +182,6 @@ mod internal { } } - pub trait Sealed {} impl Sealed for u16 {} impl Sealed for f32 {} @@ -210,22 +216,46 @@ mod tests { #[test] fn test_quality_item_from_str1() { let x: QualityValue = "chunked".parse().unwrap(); - assert_eq!(x, QualityValue { value: "chunked".to_owned(), quality: Quality(1000), }); + assert_eq!( + x, + QualityValue { + value: "chunked".to_owned(), + quality: Quality(1000), + } + ); } #[test] fn test_quality_item_from_str2() { let x: QualityValue = "chunked; q=1".parse().unwrap(); - assert_eq!(x, QualityValue { value: "chunked".to_owned(), quality: Quality(1000), }); + assert_eq!( + x, + QualityValue { + value: "chunked".to_owned(), + quality: Quality(1000), + } + ); } #[test] fn test_quality_item_from_str3() { let x: QualityValue = "gzip; q=0.5".parse().unwrap(); - assert_eq!(x, QualityValue { value: "gzip".to_owned(), quality: Quality(500), }); + assert_eq!( + x, + QualityValue { + value: "gzip".to_owned(), + quality: Quality(500), + } + ); } #[test] fn test_quality_item_from_str4() { let x: QualityValue = "gzip; q=0.273".parse().unwrap(); - assert_eq!(x, QualityValue { value: "gzip".to_owned(), quality: Quality(273), }); + assert_eq!( + x, + QualityValue { + value: "gzip".to_owned(), + quality: Quality(273), + } + ); } #[test] fn test_quality_item_from_str5() { From c4ac5826bcf803a52ab31e3104cde7624c619529 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Fri, 18 Oct 2019 15:05:33 +0200 Subject: [PATCH 2/4] Enable the Accept tests --- src/common/accept.rs | 79 +++++++++++++++++++++++++++++++++++++-- src/util/quality_value.rs | 2 +- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/common/accept.rs b/src/common/accept.rs index b13504cb..c631fe02 100644 --- a/src/common/accept.rs +++ b/src/common/accept.rs @@ -2,7 +2,7 @@ use http::HttpTryFrom; use mime::{self, Mime}; -use util::QualityValue; +use {util::QualityValue, Header}; fn qitem(mime: Mime) -> QualityValue { QualityValue::new(mime, Default::default()) @@ -84,10 +84,10 @@ fn qitem(mime: Mime) -> QualityValue { /// ]) /// ); /// ``` -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct Accept(pub Vec>); -impl ::Header for Accept { +impl Header for Accept { fn name() -> &'static ::HeaderName { &::http::header::ACCEPT } @@ -141,3 +141,76 @@ impl Accept { Accept(vec![qitem(mime::IMAGE_STAR)]) } } + +#[cfg(test)] +mod tests { + use super::*; + + use { + http::HeaderValue, + mime::{TEXT_HTML, TEXT_PLAIN, TEXT_PLAIN_UTF_8}, + }; + + use util::Quality; + + macro_rules! test_header { + ($name: ident, $input: expr, $expected: expr) => { + #[test] + fn $name() { + assert_eq!( + Accept::decode( + &mut $input + .into_iter() + .map(|s| HeaderValue::from_bytes(s).unwrap()) + .collect::>() + .iter() + ) + .ok(), + $expected, + ); + } + }; + } + + // Tests from the RFC + test_header!( + test1, + vec![b"audio/*; q=0.2, audio/basic"], + Some(Accept(vec![ + QualityValue::new("audio/*".parse().unwrap(), Quality::from(200)), + qitem("audio/basic".parse().unwrap()), + ])) + ); + test_header!( + test2, + vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], + Some(Accept(vec![ + QualityValue::new(TEXT_PLAIN, Quality::from(500)), + qitem(TEXT_HTML), + QualityValue::new("text/x-dvi".parse().unwrap(), Quality::from(800)), + qitem("text/x-c".parse().unwrap()), + ])) + ); + // Custom tests + test_header!( + test3, + vec![b"text/plain; charset=utf-8"], + Some(Accept(vec![qitem(TEXT_PLAIN_UTF_8),])) + ); + test_header!( + test4, + vec![b"text/plain; charset=utf-8; q=0.5"], + Some(Accept(vec![QualityValue::new( + TEXT_PLAIN_UTF_8, + Quality::from(500) + ),])) + ); + + #[test] + #[ignore] + fn test_fuzzing1() { + let raw = HeaderValue::from_static("chunk#;e"); + let header = Accept::decode(&mut Some(&raw).into_iter()); + assert!(header.is_ok()); + } +} diff --git a/src/util/quality_value.rs b/src/util/quality_value.rs index 499bae44..ce740451 100644 --- a/src/util/quality_value.rs +++ b/src/util/quality_value.rs @@ -32,7 +32,7 @@ impl Default for Quality { /// Represents an item with a quality value as defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug)] pub struct QualityValue { /// The actual contents of the field. pub value: T, From 99c59e27abf42e0e5c16376f0577de41790a7f5f Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Mon, 21 Oct 2019 15:58:46 +0200 Subject: [PATCH 3/4] Don't expose the internals of Accept --- src/common/accept.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/common/accept.rs b/src/common/accept.rs index c631fe02..13ebde93 100644 --- a/src/common/accept.rs +++ b/src/common/accept.rs @@ -1,3 +1,5 @@ +use std::iter::FromIterator; + use http::HttpTryFrom; use mime::{self, Mime}; @@ -38,12 +40,13 @@ fn qitem(mime: Mime) -> QualityValue { /// # extern crate headers; /// extern crate mime; /// extern crate http; +/// use std::iter::FromIterator; /// use headers::{Accept, QualityValue, HeaderMapExt}; /// /// let mut headers = http::HeaderMap::new(); /// /// headers.typed_insert( -/// Accept(vec![ +/// Accept::from_iter(vec![ /// QualityValue::new(mime::TEXT_HTML, Default::default()), /// ]) /// ); @@ -52,11 +55,12 @@ fn qitem(mime: Mime) -> QualityValue { /// ``` /// # extern crate headers; /// extern crate mime; +/// use std::iter::FromIterator; /// use headers::{Accept, QualityValue, HeaderMapExt}; /// /// let mut headers = http::HeaderMap::new(); /// headers.typed_insert( -/// Accept(vec![ +/// Accept::from_iter(vec![ /// QualityValue::new(mime::APPLICATION_JSON, Default::default()), /// ]) /// ); @@ -64,12 +68,13 @@ fn qitem(mime: Mime) -> QualityValue { /// ``` /// # extern crate headers; /// extern crate mime; +/// use std::iter::FromIterator; /// use headers::{Accept, QualityValue, HeaderMapExt}; /// /// let mut headers = http::HeaderMap::new(); /// /// headers.typed_insert( -/// Accept(vec![ +/// Accept::from_iter(vec![ /// QualityValue::from(mime::TEXT_HTML), /// QualityValue::from("application/xhtml+xml".parse::().unwrap()), /// QualityValue::new( @@ -85,7 +90,7 @@ fn qitem(mime: Mime) -> QualityValue { /// ); /// ``` #[derive(Debug, PartialEq, Eq)] -pub struct Accept(pub Vec>); +pub struct Accept(Vec>); impl Header for Accept { fn name() -> &'static ::HeaderName { @@ -120,6 +125,15 @@ impl Header for Accept { } } +impl FromIterator> for Accept { + fn from_iter(iter: T) -> Self + where + T: IntoIterator>, + { + Accept(iter.into_iter().collect()) + } +} + impl Accept { /// A constructor to easily create `Accept: */*`. pub fn star() -> Accept { @@ -140,6 +154,11 @@ impl Accept { pub fn image() -> Accept { Accept(vec![qitem(mime::IMAGE_STAR)]) } + + /// Returns an iterator over the quality values + pub fn iter(&self) -> impl Iterator> { + self.0.iter() + } } #[cfg(test)] From 5f9b3b7f586f3ee6a0208c16c6a29b99840f5120 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Thu, 24 Oct 2019 14:57:08 +0200 Subject: [PATCH 4/4] perf: Avoid allocating in QualityValue --- src/util/quality_value.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/util/quality_value.rs b/src/util/quality_value.rs index ce740451..da98173e 100644 --- a/src/util/quality_value.rs +++ b/src/util/quality_value.rs @@ -94,13 +94,13 @@ impl str::FromStr for QualityValue { let mut raw_item = s; let mut quality = 1f32; - let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); - if parts.len() == 2 { - if parts[0].len() < 2 { + let mut parts = s.rsplitn(2, ';').map(|x| x.trim()); + if let (Some(first), Some(second), None) = (parts.next(), parts.next(), parts.next()) { + if first.len() < 2 { return Err(::Error::invalid()); } - if parts[0].starts_with("q=") || parts[0].starts_with("Q=") { - let q_part = &parts[0][2..parts[0].len()]; + if first.starts_with("q=") || first.starts_with("Q=") { + let q_part = &first[2..]; if q_part.len() > 5 { return Err(::Error::invalid()); } @@ -108,7 +108,7 @@ impl str::FromStr for QualityValue { Ok(q_value) => { if 0f32 <= q_value && q_value <= 1f32 { quality = q_value; - raw_item = parts[1]; + raw_item = second; } else { return Err(::Error::invalid()); }