From 3721106795c846088c1ae98dbab1a13e960323c8 Mon Sep 17 00:00:00 2001 From: Luro02 <24826124+Luro02@users.noreply.github.com> Date: Sat, 14 Sep 2019 11:31:16 +0200 Subject: [PATCH] Rewrote `AttributePairs` --- src/attribute.rs | 196 +++++++++++------- src/error.rs | 13 ++ .../master_playlist/i_frame_stream_inf.rs | 6 +- src/tags/master_playlist/media.rs | 7 +- src/tags/master_playlist/session_data.rs | 8 +- src/tags/master_playlist/stream_inf.rs | 14 +- src/tags/media_segment/date_range.rs | 6 +- src/tags/media_segment/key.rs | 27 ++- src/tags/media_segment/map.rs | 7 +- src/tags/shared/start.rs | 7 +- src/types/decryption_key.rs | 6 +- 11 files changed, 168 insertions(+), 129 deletions(-) diff --git a/src/attribute.rs b/src/attribute.rs index 212728b..338fed5 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -1,94 +1,136 @@ -use crate::{Error, Result}; -use std::collections::HashSet; -use std::str; +use std::collections::HashMap; +use std::ops::{Deref, DerefMut}; +use std::str::FromStr; -#[derive(Debug)] -pub struct AttributePairs<'a> { - input: &'a str, - visited_keys: HashSet<&'a str>, -} -impl<'a> AttributePairs<'a> { - pub fn parse(input: &'a str) -> Self { - AttributePairs { - input, - visited_keys: HashSet::new(), - } - } +use crate::Error; - fn parse_name(&mut self) -> Result<&'a str> { - for i in 0..self.input.len() { - match self.input.as_bytes()[i] { - b'=' => { - let (key, _) = self.input.split_at(i); - let (_, rest) = self.input.split_at(i + 1); - self.input = rest; - return Ok(key); - } - b'A'..=b'Z' | b'0'..=b'9' | b'-' => {} - _ => { - return Err(Error::invalid_attribute(self.input.to_string())); - } - } - } +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct AttributePairs(HashMap); - Err(Error::missing_value(self.input.to_string())) - } - - fn parse_raw_value(&mut self) -> &'a str { - let mut in_quote = false; - let mut value_end = self.input.len(); - let mut next = self.input.len(); - for (i, c) in self.input.bytes().enumerate() { - match c { - b'"' => { - in_quote = !in_quote; - } - b',' if !in_quote => { - value_end = i; - next = i + 1; - break; - } - _ => {} - } - } - let (value, _) = self.input.split_at(value_end); - let (_, rest) = self.input.split_at(next); - self.input = rest; - value +impl AttributePairs { + pub fn new() -> Self { + Self::default() } } -impl<'a> Iterator for AttributePairs<'a> { - type Item = Result<(&'a str, &'a str)>; - fn next(&mut self) -> Option { - if self.input.is_empty() { - return None; - } +impl Deref for AttributePairs { + type Target = HashMap; - let result = || -> Result<(&'a str, &'a str)> { - let key = self.parse_name()?; - self.visited_keys.insert(key); - - let value = self.parse_raw_value(); - Ok((key, value)) - }(); - Some(result) + fn deref(&self) -> &Self::Target { + &self.0 } } +impl DerefMut for AttributePairs { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl IntoIterator for AttributePairs { + type Item = (String, String); + type IntoIter = ::std::collections::hash_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a> IntoIterator for &'a AttributePairs { + type Item = (&'a String, &'a String); + type IntoIter = ::std::collections::hash_map::Iter<'a, String, String>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl FromStr for AttributePairs { + type Err = Error; + + fn from_str(input: &str) -> Result { + let mut result = AttributePairs::new(); + + for line in split(input) { + let pair = line.trim().split("=").collect::>(); + + if pair.len() < 2 { + return Err(Error::invalid_input()); + } + + let key = pair[0].to_uppercase(); + let value = pair[1].to_string(); + + result.insert(key.to_string(), value.to_string()); + } + Ok(result) + } +} + +fn split(value: &str) -> Vec { + let mut result = vec![]; + + let mut inside_quotes = false; + let mut temp_string = String::new(); + + for c in value.chars() { + match c { + '"' => { + if inside_quotes { + inside_quotes = false; + } else { + inside_quotes = true; + } + temp_string.push(c); + } + ',' => { + if !inside_quotes { + result.push(temp_string); + temp_string = String::new(); + } else { + temp_string.push(c); + } + } + _ => { + temp_string.push(c); + } + } + } + result.push(temp_string); + + result +} + #[cfg(test)] mod test { use super::*; #[test] - fn it_works() { - let mut pairs = AttributePairs::parse("FOO=BAR,BAR=\"baz,qux\",ABC=12.3"); - assert_eq!(pairs.next().map(|x| x.ok()), Some(Some(("FOO", "BAR")))); - assert_eq!( - pairs.next().map(|x| x.ok()), - Some(Some(("BAR", "\"baz,qux\""))) - ); - assert_eq!(pairs.next().map(|x| x.ok()), Some(Some(("ABC", "12.3")))); - assert_eq!(pairs.next().map(|x| x.ok()), None) + fn test_parser() { + let pairs = ("FOO=BAR,BAR=\"baz,qux\",ABC=12.3") + .parse::() + .unwrap(); + + let mut iterator = pairs.iter(); + assert!(iterator.any(|(k, v)| k == "FOO" && "BAR" == v)); + + let mut iterator = pairs.iter(); + assert!(iterator.any(|(k, v)| k == "BAR" && v == "\"baz,qux\"")); + + let mut iterator = pairs.iter(); + assert!(iterator.any(|(k, v)| k == "ABC" && v == "12.3")); + } + + #[test] + fn test_iterator() { + let mut attrs = AttributePairs::new(); + attrs.insert("key_01".to_string(), "value_01".to_string()); + attrs.insert("key_02".to_string(), "value_02".to_string()); + + let mut iterator = attrs.iter(); + assert!(iterator.any(|(k, v)| k == "key_01" && v == "value_01")); + + let mut iterator = attrs.iter(); + assert!(iterator.any(|(k, v)| k == "key_02" && v == "value_02")); } } diff --git a/src/error.rs b/src/error.rs index ff1fddf..d4666c7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -46,6 +46,9 @@ pub enum ErrorKind { #[fail(display = "Unknown Protocol version: {:?}", _0)] UnknownProtocolVersion(String), + #[fail(display = "IoError: {}", _0)] + Io(String), + /// Hints that destructuring should not be exhaustive. /// /// This enum may grow additional variants, so this makes sure clients @@ -164,6 +167,10 @@ impl Error { pub(crate) fn unknown_protocol_version(value: T) -> Self { Self::from(ErrorKind::UnknownProtocolVersion(value.to_string())) } + + pub(crate) fn io(value: T) -> Self { + Self::from(ErrorKind::Io(value.to_string())) + } } impl From for Error { @@ -177,3 +184,9 @@ impl From for Error { Error::parse_float_error(value) } } + +impl From for Error { + fn from(value: ::std::io::Error) -> Self { + Error::io(value) + } +} diff --git a/src/tags/master_playlist/i_frame_stream_inf.rs b/src/tags/master_playlist/i_frame_stream_inf.rs index 7f9abd3..93e5425 100644 --- a/src/tags/master_playlist/i_frame_stream_inf.rs +++ b/src/tags/master_playlist/i_frame_stream_inf.rs @@ -94,10 +94,8 @@ impl FromStr for ExtXIFrameStreamInf { let mut hdcp_level = None; let mut video = None; - let attrs = AttributePairs::parse(input); - for attr in attrs { - let (key, value) = attr?; - match key { + for (key, value) in input.parse::()? { + match key.as_str() { "URI" => uri = Some(unquote(value)), "BANDWIDTH" => bandwidth = Some(parse_u64(value)?), "AVERAGE-BANDWIDTH" => average_bandwidth = Some(parse_u64(value)?), diff --git a/src/tags/master_playlist/media.rs b/src/tags/master_playlist/media.rs index 40d1e41..f63f1f3 100644 --- a/src/tags/master_playlist/media.rs +++ b/src/tags/master_playlist/media.rs @@ -331,10 +331,9 @@ impl FromStr for ExtXMedia { let input = tag(input, Self::PREFIX)?; let mut builder = ExtXMediaBuilder::new(); - let attrs = AttributePairs::parse(input); - for attr in attrs { - let (key, value) = attr?; - match key { + + for (key, value) in input.parse::()? { + match key.as_str() { "TYPE" => { builder.media_type(value.parse()?); } diff --git a/src/tags/master_playlist/session_data.rs b/src/tags/master_playlist/session_data.rs index f246862..12d138d 100644 --- a/src/tags/master_playlist/session_data.rs +++ b/src/tags/master_playlist/session_data.rs @@ -77,10 +77,8 @@ impl FromStr for ExtXSessionData { let mut uri = None; let mut language = None; - let attrs = AttributePairs::parse(input); - for attr in attrs { - let (key, value) = attr?; - match key { + for (key, value) in input.parse::()? { + match key.as_str() { "DATA-ID" => data_id = Some(unquote(value)), "VALUE" => session_value = Some(unquote(value)), "URI" => uri = Some(unquote(value)), @@ -92,7 +90,7 @@ impl FromStr for ExtXSessionData { } } - let data_id = data_id.ok_or(Error::missing_value("DATA-ID"))?; + let data_id = data_id.ok_or(Error::missing_value("EXT-X-DATA-ID"))?; let data = { if let Some(value) = session_value { if uri.is_some() { diff --git a/src/tags/master_playlist/stream_inf.rs b/src/tags/master_playlist/stream_inf.rs index e69df8b..a172da9 100644 --- a/src/tags/master_playlist/stream_inf.rs +++ b/src/tags/master_playlist/stream_inf.rs @@ -149,10 +149,10 @@ impl FromStr for ExtXStreamInf { fn from_str(input: &str) -> Result { let mut lines = input.lines(); - let first_line = lines.next().ok_or(Error::invalid_input())?; // TODO! - let second_line = lines.next().ok_or(Error::invalid_input())?; // TODO! + let first_line = lines.next().ok_or(Error::missing_value("first_line"))?; + let second_line = lines.next().ok_or(Error::missing_value("second_line"))?; - tag(first_line, Self::PREFIX)?; + let first_line = tag(first_line, Self::PREFIX)?; let uri = SingleLineString::new(second_line)?; @@ -167,10 +167,8 @@ impl FromStr for ExtXStreamInf { let mut subtitles = None; let mut closed_captions = None; - let attrs = AttributePairs::parse(first_line.split_at(Self::PREFIX.len()).1); - for attr in attrs { - let (key, value) = (attr)?; - match key { + for (key, value) in first_line.parse::()? { + match key.as_str() { "BANDWIDTH" => bandwidth = Some((parse_u64(value))?), "AVERAGE-BANDWIDTH" => average_bandwidth = Some((parse_u64(value))?), "CODECS" => codecs = Some(unquote(value)), @@ -188,7 +186,7 @@ impl FromStr for ExtXStreamInf { } } - let bandwidth = bandwidth.ok_or(Error::missing_value("BANDWIDTH"))?; + let bandwidth = bandwidth.ok_or(Error::missing_value("EXT-X-BANDWIDTH"))?; Ok(ExtXStreamInf { uri, diff --git a/src/tags/media_segment/date_range.rs b/src/tags/media_segment/date_range.rs index 8fa6f11..841b513 100644 --- a/src/tags/media_segment/date_range.rs +++ b/src/tags/media_segment/date_range.rs @@ -96,11 +96,9 @@ impl FromStr for ExtXDateRange { let mut end_on_next = false; let mut client_attributes = BTreeMap::new(); - let attrs = AttributePairs::parse(input); - for attr in attrs { - let (key, value) = attr?; - match key { + for (key, value) in input.parse::()? { + match key.as_str() { "ID" => id = Some(unquote(value)), "CLASS" => class = Some(unquote(value)), "START-DATE" => start_date = Some(unquote(value)), diff --git a/src/tags/media_segment/key.rs b/src/tags/media_segment/key.rs index fb47c83..8e59522 100644 --- a/src/tags/media_segment/key.rs +++ b/src/tags/media_segment/key.rs @@ -10,33 +10,31 @@ use crate::Error; /// /// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4 #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ExtXKey { - key: Option, -} +pub struct ExtXKey(Option); impl ExtXKey { pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:"; /// Makes a new `ExtXKey` tag. pub const fn new(key: DecryptionKey) -> Self { - ExtXKey { key: Some(key) } + Self(Some(key)) } /// Makes a new `ExtXKey` tag without a decryption key. /// /// This tag has the `METHDO=NONE` attribute. pub const fn new_without_key() -> Self { - ExtXKey { key: None } + Self(None) } /// Returns the decryption key for the following media segments and media initialization sections. pub fn key(&self) -> Option<&DecryptionKey> { - self.key.as_ref() + self.0.as_ref() } /// Returns the protocol compatibility version that this tag requires. pub fn requires_version(&self) -> ProtocolVersion { - self.key + self.0 .as_ref() .map_or(ProtocolVersion::V1, |k| k.requires_version()) } @@ -45,7 +43,7 @@ impl ExtXKey { impl fmt::Display for ExtXKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", Self::PREFIX)?; - if let Some(ref key) = self.key { + if let Some(ref key) = self.0 { write!(f, "{}", key)?; } else { write!(f, "METHOD=NONE")?; @@ -60,17 +58,18 @@ impl FromStr for ExtXKey { fn from_str(input: &str) -> Result { let input = tag(input, Self::PREFIX)?; - if AttributePairs::parse(input).any(|a| a.as_ref().ok() == Some(&("METHOD", "NONE"))) { - for attr in AttributePairs::parse(input) { - let (key, _) = attr?; + let pairs = input.parse::()?; + + if pairs.iter().any(|(k, v)| k == "METHOD" && v == "NONE") { + for (key, _) in pairs { if key == "URI" || key == "IV" || key == "KEYFORMAT" || key == "KEYFORMATVERSIONS" { return Err(Error::invalid_input()); } } - Ok(ExtXKey { key: None }) + + Ok(Self(None)) } else { - let key = input.parse()?; - Ok(ExtXKey { key: Some(key) }) + Ok(Self(Some(input.parse()?))) } } } diff --git a/src/tags/media_segment/map.rs b/src/tags/media_segment/map.rs index 728afa4..97ca7fc 100644 --- a/src/tags/media_segment/map.rs +++ b/src/tags/media_segment/map.rs @@ -69,10 +69,9 @@ impl FromStr for ExtXMap { let mut uri = None; let mut range = None; - let attrs = AttributePairs::parse(input); - for attr in attrs { - let (key, value) = (attr)?; - match key { + + for (key, value) in input.parse::()? { + match key.as_str() { "URI" => uri = Some(unquote(value)), "BYTERANGE" => { range = Some((unquote(value).parse())?); diff --git a/src/tags/shared/start.rs b/src/tags/shared/start.rs index f946992..06b6d90 100644 --- a/src/tags/shared/start.rs +++ b/src/tags/shared/start.rs @@ -71,11 +71,8 @@ impl FromStr for ExtXStart { let mut time_offset = None; let mut precise = false; - let attrs = AttributePairs::parse(input); - - for attr in attrs { - let (key, value) = (attr)?; - match key { + for (key, value) in input.parse::()? { + match key.as_str() { "TIME-OFFSET" => time_offset = Some((value.parse())?), "PRECISE" => precise = (parse_yes_or_no(value))?, _ => { diff --git a/src/types/decryption_key.rs b/src/types/decryption_key.rs index 87c7116..014a61f 100644 --- a/src/types/decryption_key.rs +++ b/src/types/decryption_key.rs @@ -60,10 +60,8 @@ impl FromStr for DecryptionKey { let mut key_format = None; let mut key_format_versions = None; - let attrs = AttributePairs::parse(input); - for attr in attrs { - let (key, value) = (attr)?; - match key { + for (key, value) in input.parse::()? { + match key.as_str() { "METHOD" => method = Some((value.parse())?), "URI" => uri = Some(unquote(value)), "IV" => iv = Some((value.parse())?),