From 1966a7608de4e1323d51cefe7375ecec741472c3 Mon Sep 17 00:00:00 2001 From: Luro02 <24826124+Luro02@users.noreply.github.com> Date: Sun, 8 Sep 2019 11:30:52 +0200 Subject: [PATCH] removed QuotedString --- src/master_playlist.rs | 10 +- .../master_playlist/i_frame_stream_inf.rs | 38 +++--- src/tags/master_playlist/media.rs | 119 +++++++++--------- src/tags/master_playlist/session_data.rs | 55 ++++---- src/tags/master_playlist/session_key.rs | 9 +- src/tags/master_playlist/stream_inf.rs | 36 +++--- src/tags/media_segment/date_range.rs | 45 +++---- src/tags/media_segment/key.rs | 10 +- src/tags/media_segment/map.rs | 31 ++--- src/types/closed_captions.rs | 12 +- src/types/decryption_key.rs | 21 ++-- src/types/mod.rs | 2 - src/types/quoted_string.rs | 62 --------- src/types/session_data.rs | 6 +- src/utils.rs | 72 ++++++++++- 15 files changed, 256 insertions(+), 272 deletions(-) delete mode 100644 src/types/quoted_string.rs diff --git a/src/master_playlist.rs b/src/master_playlist.rs index d33e0ff..40fc065 100644 --- a/src/master_playlist.rs +++ b/src/master_playlist.rs @@ -3,7 +3,7 @@ use crate::tags::{ ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData, ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion, MasterPlaylistTag, }; -use crate::types::{ClosedCaptions, MediaType, ProtocolVersion, QuotedString}; +use crate::types::{ClosedCaptions, MediaType, ProtocolVersion}; use crate::{Error, ErrorKind, Result}; use std::collections::HashSet; use std::fmt; @@ -22,6 +22,7 @@ pub struct MasterPlaylistBuilder { session_data_tags: Vec, session_key_tags: Vec, } + impl MasterPlaylistBuilder { /// Makes a new `MasterPlaylistBuilder` instance. pub fn new() -> Self { @@ -209,12 +210,13 @@ impl MasterPlaylistBuilder { Ok(()) } - fn check_media_group(&self, media_type: MediaType, group_id: &QuotedString) -> bool { + fn check_media_group(&self, media_type: MediaType, group_id: T) -> bool { self.media_tags .iter() - .any(|t| t.media_type() == media_type && t.group_id() == group_id) + .any(|t| t.media_type() == media_type && t.group_id() == &group_id.to_string()) } } + impl Default for MasterPlaylistBuilder { fn default() -> Self { Self::new() @@ -233,6 +235,7 @@ pub struct MasterPlaylist { session_data_tags: Vec, session_key_tags: Vec, } + impl MasterPlaylist { /// Returns the `EXT-X-VERSION` tag contained in the playlist. pub fn version_tag(&self) -> ExtXVersion { @@ -274,6 +277,7 @@ impl MasterPlaylist { &self.session_key_tags } } + impl fmt::Display for MasterPlaylist { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "{}", ExtM3u)?; diff --git a/src/tags/master_playlist/i_frame_stream_inf.rs b/src/tags/master_playlist/i_frame_stream_inf.rs index 3850e47..e287fcb 100644 --- a/src/tags/master_playlist/i_frame_stream_inf.rs +++ b/src/tags/master_playlist/i_frame_stream_inf.rs @@ -1,6 +1,7 @@ use crate::attribute::AttributePairs; -use crate::types::{DecimalResolution, HdcpLevel, ProtocolVersion, QuotedString}; +use crate::types::{DecimalResolution, HdcpLevel, ProtocolVersion}; use crate::utils::parse_u64; +use crate::utils::{quote, unquote}; use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; @@ -10,22 +11,22 @@ use std::str::FromStr; /// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.3 #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExtXIFrameStreamInf { - uri: QuotedString, + uri: String, bandwidth: u64, average_bandwidth: Option, - codecs: Option, + codecs: Option, resolution: Option, hdcp_level: Option, - video: Option, + video: Option, } impl ExtXIFrameStreamInf { pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:"; /// Makes a new `ExtXIFrameStreamInf` tag. - pub fn new(uri: QuotedString, bandwidth: u64) -> Self { + pub fn new(uri: T, bandwidth: u64) -> Self { ExtXIFrameStreamInf { - uri, + uri: uri.to_string(), bandwidth, average_bandwidth: None, codecs: None, @@ -36,7 +37,7 @@ impl ExtXIFrameStreamInf { } /// Returns the URI that identifies the associated media playlist. - pub fn uri(&self) -> &QuotedString { + pub fn uri(&self) -> &String { &self.uri } @@ -51,7 +52,7 @@ impl ExtXIFrameStreamInf { } /// Returns a string that represents the list of codec types contained the variant stream. - pub fn codecs(&self) -> Option<&QuotedString> { + pub fn codecs(&self) -> Option<&String> { self.codecs.as_ref() } @@ -66,7 +67,7 @@ impl ExtXIFrameStreamInf { } /// Returns the group identifier for the video in the variant stream. - pub fn video(&self) -> Option<&QuotedString> { + pub fn video(&self) -> Option<&String> { self.video.as_ref() } @@ -79,13 +80,14 @@ impl ExtXIFrameStreamInf { impl fmt::Display for ExtXIFrameStreamInf { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", Self::PREFIX)?; - write!(f, "URI={}", self.uri)?; + write!(f, "URI={}", quote(&self.uri))?; write!(f, ",BANDWIDTH={}", self.bandwidth)?; + if let Some(ref x) = self.average_bandwidth { write!(f, ",AVERAGE-BANDWIDTH={}", x)?; } if let Some(ref x) = self.codecs { - write!(f, ",CODECS={}", x)?; + write!(f, ",CODECS={}", quote(x))?; } if let Some(ref x) = self.resolution { write!(f, ",RESOLUTION={}", x)?; @@ -94,7 +96,7 @@ impl fmt::Display for ExtXIFrameStreamInf { write!(f, ",HDCP-LEVEL={}", x)?; } if let Some(ref x) = self.video { - write!(f, ",VIDEO={}", x)?; + write!(f, ",VIDEO={}", quote(x))?; } Ok(()) } @@ -116,13 +118,13 @@ impl FromStr for ExtXIFrameStreamInf { for attr in attrs { let (key, value) = track!(attr)?; match key { - "URI" => uri = Some(track!(value.parse())?), + "URI" => uri = Some(unquote(value)), "BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?), "AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?), - "CODECS" => codecs = Some(track!(value.parse())?), + "CODECS" => codecs = Some(unquote(value)), "RESOLUTION" => resolution = Some(track!(value.parse())?), "HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?), - "VIDEO" => video = Some(track!(value.parse())?), + "VIDEO" => video = Some(unquote(value)), _ => { // [6.3.1. General Client Responsibilities] // > ignore any attribute/value pair with an unrecognized AttributeName. @@ -150,14 +152,10 @@ mod test { #[test] fn ext_x_i_frame_stream_inf() { - let tag = ExtXIFrameStreamInf::new(quoted_string("foo"), 1000); + let tag = ExtXIFrameStreamInf::new("foo", 1000); let text = r#"#EXT-X-I-FRAME-STREAM-INF:URI="foo",BANDWIDTH=1000"#; assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V1); } - - fn quoted_string(s: &str) -> QuotedString { - QuotedString::new(s).unwrap() - } } diff --git a/src/tags/master_playlist/media.rs b/src/tags/master_playlist/media.rs index 82ee440..24d2c22 100644 --- a/src/tags/master_playlist/media.rs +++ b/src/tags/master_playlist/media.rs @@ -1,6 +1,6 @@ use crate::attribute::AttributePairs; -use crate::types::{InStreamId, MediaType, ProtocolVersion, QuotedString}; -use crate::utils::parse_yes_or_no; +use crate::types::{InStreamId, MediaType, ProtocolVersion}; +use crate::utils::{parse_yes_or_no, quote, unquote}; use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; @@ -9,17 +9,17 @@ use std::str::FromStr; #[derive(Debug, Clone)] pub struct ExtXMediaBuilder { media_type: Option, - uri: Option, - group_id: Option, - language: Option, - assoc_language: Option, - name: Option, + uri: Option, + group_id: Option, + language: Option, + assoc_language: Option, + name: Option, default: bool, autoselect: Option, forced: Option, instream_id: Option, - characteristics: Option, - channels: Option, + characteristics: Option, + channels: Option, } impl ExtXMediaBuilder { @@ -48,32 +48,32 @@ impl ExtXMediaBuilder { } /// Sets the identifier that specifies the group to which the rendition belongs. - pub fn group_id(&mut self, group_id: QuotedString) -> &mut Self { - self.group_id = Some(group_id); + pub fn group_id(&mut self, group_id: T) -> &mut Self { + self.group_id = Some(group_id.to_string()); self } /// Sets a human-readable description of the rendition. - pub fn name(&mut self, name: QuotedString) -> &mut Self { - self.name = Some(name); + pub fn name(&mut self, name: T) -> &mut Self { + self.name = Some(name.to_string()); self } /// Sets the URI that identifies the media playlist. - pub fn uri(&mut self, uri: QuotedString) -> &mut Self { - self.uri = Some(uri); + pub fn uri(&mut self, uri: T) -> &mut Self { + self.uri = Some(uri.to_string()); self } /// Sets the name of the primary language used in the rendition. - pub fn language(&mut self, language: QuotedString) -> &mut Self { - self.language = Some(language); + pub fn language(&mut self, language: T) -> &mut Self { + self.language = Some(language.to_string()); self } /// Sets the name of a language associated with the rendition. - pub fn assoc_language(&mut self, language: QuotedString) -> &mut Self { - self.assoc_language = Some(language); + pub fn assoc_language(&mut self, language: T) -> &mut Self { + self.assoc_language = Some(language.to_string()); self } @@ -102,14 +102,14 @@ impl ExtXMediaBuilder { } /// Sets the string that represents uniform type identifiers (UTI). - pub fn characteristics(&mut self, characteristics: QuotedString) -> &mut Self { - self.characteristics = Some(characteristics); + pub fn characteristics(&mut self, characteristics: T) -> &mut Self { + self.characteristics = Some(characteristics.to_string()); self } /// Sets the string that represents the parameters of the rendition. - pub fn channels(&mut self, channels: QuotedString) -> &mut Self { - self.channels = Some(channels); + pub fn channels(&mut self, channels: T) -> &mut Self { + self.channels = Some(channels.to_string()); self } @@ -159,31 +159,31 @@ impl Default for ExtXMediaBuilder { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExtXMedia { media_type: MediaType, - uri: Option, - group_id: QuotedString, - language: Option, - assoc_language: Option, - name: QuotedString, + uri: Option, + group_id: String, + language: Option, + assoc_language: Option, + name: String, default: bool, autoselect: bool, forced: bool, instream_id: Option, - characteristics: Option, - channels: Option, + characteristics: Option, + channels: Option, } impl ExtXMedia { pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:"; /// Makes a new `ExtXMedia` tag. - pub fn new(media_type: MediaType, group_id: QuotedString, name: QuotedString) -> Self { + pub fn new(media_type: MediaType, group_id: T, name: T) -> Self { ExtXMedia { media_type, uri: None, - group_id, + group_id: group_id.to_string(), language: None, assoc_language: None, - name, + name: name.to_string(), default: false, autoselect: false, forced: false, @@ -199,27 +199,27 @@ impl ExtXMedia { } /// Returns the identifier that specifies the group to which the rendition belongs. - pub fn group_id(&self) -> &QuotedString { + pub fn group_id(&self) -> &String { &self.group_id } /// Returns a human-readable description of the rendition. - pub fn name(&self) -> &QuotedString { + pub fn name(&self) -> &String { &self.name } /// Returns the URI that identifies the media playlist. - pub fn uri(&self) -> Option<&QuotedString> { + pub fn uri(&self) -> Option<&String> { self.uri.as_ref() } /// Returns the name of the primary language used in the rendition. - pub fn language(&self) -> Option<&QuotedString> { + pub fn language(&self) -> Option<&String> { self.language.as_ref() } /// Returns the name of a language associated with the rendition. - pub fn assoc_language(&self) -> Option<&QuotedString> { + pub fn assoc_language(&self) -> Option<&String> { self.assoc_language.as_ref() } @@ -247,12 +247,12 @@ impl ExtXMedia { /// Returns a string that represents uniform type identifiers (UTI). /// /// Each UTI indicates an individual characteristic of the rendition. - pub fn characteristics(&self) -> Option<&QuotedString> { + pub fn characteristics(&self) -> Option<&String> { self.characteristics.as_ref() } /// Returns a string that represents the parameters of the rendition. - pub fn channels(&self) -> Option<&QuotedString> { + pub fn channels(&self) -> Option<&String> { self.channels.as_ref() } @@ -274,16 +274,16 @@ impl fmt::Display for ExtXMedia { write!(f, "{}", Self::PREFIX)?; write!(f, "TYPE={}", self.media_type)?; if let Some(ref x) = self.uri { - write!(f, ",URI={}", x)?; + write!(f, ",URI={}", quote(x))?; } - write!(f, ",GROUP-ID={}", self.group_id)?; + write!(f, ",GROUP-ID={}", quote(&self.group_id))?; if let Some(ref x) = self.language { - write!(f, ",LANGUAGE={}", x)?; + write!(f, ",LANGUAGE={}", quote(x))?; } if let Some(ref x) = self.assoc_language { - write!(f, ",ASSOC-LANGUAGE={}", x)?; + write!(f, ",ASSOC-LANGUAGE={}", quote(x))?; } - write!(f, ",NAME={}", self.name)?; + write!(f, ",NAME={}", quote(&self.name))?; if self.default { write!(f, ",DEFAULT=YES")?; } @@ -294,13 +294,13 @@ impl fmt::Display for ExtXMedia { write!(f, ",FORCED=YES")?; } if let Some(ref x) = self.instream_id { - write!(f, ",INSTREAM-ID=\"{}\"", x)?; + write!(f, ",INSTREAM-ID={}", quote(x))?; } if let Some(ref x) = self.characteristics { - write!(f, ",CHARACTERISTICS={}", x)?; + write!(f, ",CHARACTERISTICS={}", quote(x))?; } if let Some(ref x) = self.channels { - write!(f, ",CHANNELS={}", x)?; + write!(f, ",CHANNELS={}", quote(x))?; } Ok(()) } @@ -320,19 +320,19 @@ impl FromStr for ExtXMedia { builder.media_type(track!(value.parse())?); } "URI" => { - builder.uri(track!(value.parse())?); + builder.uri(unquote(value)); } "GROUP-ID" => { - builder.group_id(track!(value.parse())?); + builder.group_id(unquote(value)); } "LANGUAGE" => { - builder.language(track!(value.parse())?); + builder.language(unquote(value)); } "ASSOC-LANGUAGE" => { - builder.assoc_language(track!(value.parse())?); + builder.assoc_language(unquote(value)); } "NAME" => { - builder.name(track!(value.parse())?); + builder.name(unquote(value)); } "DEFAULT" => { builder.default(track!(parse_yes_or_no(value))?); @@ -344,14 +344,13 @@ impl FromStr for ExtXMedia { builder.forced(track!(parse_yes_or_no(value))?); } "INSTREAM-ID" => { - let s: QuotedString = track!(value.parse())?; - builder.instream_id(track!(s.parse())?); + builder.instream_id(unquote(value).parse()?); } "CHARACTERISTICS" => { - builder.characteristics(track!(value.parse())?); + builder.characteristics(unquote(value)); } "CHANNELS" => { - builder.channels(track!(value.parse())?); + builder.channels(unquote(value)); } _ => { // [6.3.1. General Client Responsibilities] @@ -369,14 +368,10 @@ mod test { #[test] fn ext_x_media() { - let tag = ExtXMedia::new(MediaType::Audio, quoted_string("foo"), quoted_string("bar")); + let tag = ExtXMedia::new(MediaType::Audio, "foo", "bar"); let text = r#"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="foo",NAME="bar""#; assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V1); } - - fn quoted_string(s: &str) -> QuotedString { - QuotedString::new(s).unwrap() - } } diff --git a/src/tags/master_playlist/session_data.rs b/src/tags/master_playlist/session_data.rs index 3ddc153..75da90c 100644 --- a/src/tags/master_playlist/session_data.rs +++ b/src/tags/master_playlist/session_data.rs @@ -1,5 +1,6 @@ use crate::attribute::AttributePairs; -use crate::types::{ProtocolVersion, QuotedString, SessionData}; +use crate::types::{ProtocolVersion, SessionData}; +use crate::utils::{quote, unquote}; use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; @@ -9,34 +10,34 @@ use std::str::FromStr; /// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4 #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExtXSessionData { - data_id: QuotedString, + data_id: String, data: SessionData, - language: Option, + language: Option, } impl ExtXSessionData { pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-DATA:"; /// Makes a new `ExtXSessionData` tag. - pub fn new(data_id: QuotedString, data: SessionData) -> Self { + pub fn new(data_id: T, data: SessionData) -> Self { ExtXSessionData { - data_id, + data_id: data_id.to_string(), data, language: None, } } /// Makes a new `ExtXSessionData` with the given language. - pub fn with_language(data_id: QuotedString, data: SessionData, language: QuotedString) -> Self { + pub fn with_language(data_id: T, data: SessionData, language: T) -> Self { ExtXSessionData { - data_id, + data_id: data_id.to_string(), data, - language: Some(language), + language: Some(language.to_string()), } } /// Returns the identifier of the data. - pub fn data_id(&self) -> &QuotedString { + pub fn data_id(&self) -> &String { &self.data_id } @@ -46,7 +47,7 @@ impl ExtXSessionData { } /// Returns the language of the data. - pub fn language(&self) -> Option<&QuotedString> { + pub fn language(&self) -> Option<&String> { self.language.as_ref() } @@ -59,13 +60,13 @@ impl ExtXSessionData { impl fmt::Display for ExtXSessionData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", Self::PREFIX)?; - write!(f, "DATA-ID={}", self.data_id)?; + write!(f, "DATA-ID={}", quote(&self.data_id))?; match self.data { - SessionData::Value(ref x) => write!(f, ",VALUE={}", x)?, - SessionData::Uri(ref x) => write!(f, ",URI={}", x)?, + SessionData::Value(ref x) => write!(f, ",VALUE={}", quote(x))?, + SessionData::Uri(ref x) => write!(f, ",URI={}", quote(x))?, } if let Some(ref x) = self.language { - write!(f, ",LANGUAGE={}", x)?; + write!(f, ",LANGUAGE={}", quote(x))?; } Ok(()) } @@ -84,10 +85,10 @@ impl FromStr for ExtXSessionData { for attr in attrs { let (key, value) = track!(attr)?; match key { - "DATA-ID" => data_id = Some(track!(value.parse())?), - "VALUE" => session_value = Some(track!(value.parse())?), - "URI" => uri = Some(track!(value.parse())?), - "LANGUAGE" => language = Some(track!(value.parse())?), + "DATA-ID" => data_id = Some(unquote(value)), + "VALUE" => session_value = Some(unquote(value)), + "URI" => uri = Some(unquote(value)), + "LANGUAGE" => language = Some(unquote(value)), _ => { // [6.3.1. General Client Responsibilities] // > ignore any attribute/value pair with an unrecognized AttributeName. @@ -118,34 +119,22 @@ mod test { #[test] fn ext_x_session_data() { - let tag = ExtXSessionData::new( - quoted_string("foo"), - SessionData::Value(quoted_string("bar")), - ); + let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into())); let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar""#; assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V1); - let tag = - ExtXSessionData::new(quoted_string("foo"), SessionData::Uri(quoted_string("bar"))); + let tag = ExtXSessionData::new("foo", SessionData::Uri("bar".into())); let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",URI="bar""#; assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V1); - let tag = ExtXSessionData::with_language( - quoted_string("foo"), - SessionData::Value(quoted_string("bar")), - quoted_string("baz"), - ); + let tag = ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz"); let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar",LANGUAGE="baz""#; assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V1); } - - fn quoted_string(s: &str) -> QuotedString { - QuotedString::new(s).unwrap() - } } diff --git a/src/tags/master_playlist/session_key.rs b/src/tags/master_playlist/session_key.rs index dc0838d..aafae51 100644 --- a/src/tags/master_playlist/session_key.rs +++ b/src/tags/master_playlist/session_key.rs @@ -38,6 +38,7 @@ impl fmt::Display for ExtXSessionKey { impl FromStr for ExtXSessionKey { type Err = Error; + fn from_str(s: &str) -> Result { track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); let suffix = s.split_at(Self::PREFIX.len()).1; @@ -49,13 +50,13 @@ impl FromStr for ExtXSessionKey { #[cfg(test)] mod test { use super::*; - use crate::types::{EncryptionMethod, InitializationVector, QuotedString}; + use crate::types::{EncryptionMethod, InitializationVector}; #[test] fn ext_x_session_key() { let tag = ExtXSessionKey::new(DecryptionKey { method: EncryptionMethod::Aes128, - uri: quoted_string("foo"), + uri: "foo".to_string(), iv: Some(InitializationVector([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ])), @@ -68,8 +69,4 @@ mod test { assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V2); } - - fn quoted_string(s: &str) -> QuotedString { - QuotedString::new(s).unwrap() - } } diff --git a/src/tags/master_playlist/stream_inf.rs b/src/tags/master_playlist/stream_inf.rs index d919e4d..28e5bcd 100644 --- a/src/tags/master_playlist/stream_inf.rs +++ b/src/tags/master_playlist/stream_inf.rs @@ -1,9 +1,9 @@ use crate::attribute::AttributePairs; use crate::types::{ ClosedCaptions, DecimalFloatingPoint, DecimalResolution, HdcpLevel, ProtocolVersion, - QuotedString, SingleLineString, + SingleLineString, }; -use crate::utils::parse_u64; +use crate::utils::{parse_u64, quote, unquote}; use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; @@ -16,13 +16,13 @@ pub struct ExtXStreamInf { uri: SingleLineString, bandwidth: u64, average_bandwidth: Option, - codecs: Option, + codecs: Option, resolution: Option, frame_rate: Option, hdcp_level: Option, - audio: Option, - video: Option, - subtitles: Option, + audio: Option, + video: Option, + subtitles: Option, closed_captions: Option, } @@ -62,7 +62,7 @@ impl ExtXStreamInf { } /// Returns a string that represents the list of codec types contained the variant stream. - pub fn codecs(&self) -> Option<&QuotedString> { + pub fn codecs(&self) -> Option<&String> { self.codecs.as_ref() } @@ -82,17 +82,17 @@ impl ExtXStreamInf { } /// Returns the group identifier for the audio in the variant stream. - pub fn audio(&self) -> Option<&QuotedString> { + pub fn audio(&self) -> Option<&String> { self.audio.as_ref() } /// Returns the group identifier for the video in the variant stream. - pub fn video(&self) -> Option<&QuotedString> { + pub fn video(&self) -> Option<&String> { self.video.as_ref() } /// Returns the group identifier for the subtitles in the variant stream. - pub fn subtitles(&self) -> Option<&QuotedString> { + pub fn subtitles(&self) -> Option<&String> { self.subtitles.as_ref() } @@ -115,7 +115,7 @@ impl fmt::Display for ExtXStreamInf { write!(f, ",AVERAGE-BANDWIDTH={}", x)?; } if let Some(ref x) = self.codecs { - write!(f, ",CODECS={}", x)?; + write!(f, ",CODECS={}", quote(x))?; } if let Some(ref x) = self.resolution { write!(f, ",RESOLUTION={}", x)?; @@ -127,13 +127,13 @@ impl fmt::Display for ExtXStreamInf { write!(f, ",HDCP-LEVEL={}", x)?; } if let Some(ref x) = self.audio { - write!(f, ",AUDIO={}", x)?; + write!(f, ",AUDIO={}", quote(x))?; } if let Some(ref x) = self.video { - write!(f, ",VIDEO={}", x)?; + write!(f, ",VIDEO={}", quote(x))?; } if let Some(ref x) = self.subtitles { - write!(f, ",SUBTITLES={}", x)?; + write!(f, ",SUBTITLES={}", quote(x))?; } if let Some(ref x) = self.closed_captions { write!(f, ",CLOSED-CAPTIONS={}", x)?; @@ -171,13 +171,13 @@ impl FromStr for ExtXStreamInf { match key { "BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?), "AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?), - "CODECS" => codecs = Some(track!(value.parse())?), + "CODECS" => codecs = Some(unquote(value)), "RESOLUTION" => resolution = Some(track!(value.parse())?), "FRAME-RATE" => frame_rate = Some(track!(value.parse())?), "HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?), - "AUDIO" => audio = Some(track!(value.parse())?), - "VIDEO" => video = Some(track!(value.parse())?), - "SUBTITLES" => subtitles = Some(track!(value.parse())?), + "AUDIO" => audio = Some(unquote(value)), + "VIDEO" => video = Some(unquote(value)), + "SUBTITLES" => subtitles = Some(unquote(value)), "CLOSED-CAPTIONS" => closed_captions = Some(track!(value.parse())?), _ => { // [6.3.1. General Client Responsibilities] diff --git a/src/tags/media_segment/date_range.rs b/src/tags/media_segment/date_range.rs index b406289..788bd8c 100644 --- a/src/tags/media_segment/date_range.rs +++ b/src/tags/media_segment/date_range.rs @@ -1,5 +1,6 @@ use crate::attribute::AttributePairs; -use crate::types::{DecimalFloatingPoint, ProtocolVersion, QuotedString}; +use crate::types::{DecimalFloatingPoint, ProtocolVersion}; +use crate::utils::{quote, unquote}; use crate::{Error, ErrorKind, Result}; use std::collections::BTreeMap; use std::fmt; @@ -14,15 +15,15 @@ use std::time::Duration; #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExtXDateRange { - pub id: QuotedString, - pub class: Option, - pub start_date: QuotedString, - pub end_date: Option, + pub id: String, + pub class: Option, + pub start_date: String, + pub end_date: Option, pub duration: Option, pub planned_duration: Option, - pub scte35_cmd: Option, - pub scte35_out: Option, - pub scte35_in: Option, + pub scte35_cmd: Option, + pub scte35_out: Option, + pub scte35_in: Option, pub end_on_next: bool, pub client_attributes: BTreeMap, } @@ -39,13 +40,13 @@ impl ExtXDateRange { impl fmt::Display for ExtXDateRange { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", Self::PREFIX)?; - write!(f, "ID={}", self.id)?; + write!(f, "ID={}", quote(&self.id))?; if let Some(ref x) = self.class { - write!(f, ",CLASS={}", x)?; + write!(f, ",CLASS={}", quote(x))?; } - write!(f, ",START-DATE={}", self.start_date)?; + write!(f, ",START-DATE={}", quote(&self.start_date))?; if let Some(ref x) = self.end_date { - write!(f, ",END-DATE={}", x)?; + write!(f, ",END-DATE={}", quote(x))?; } if let Some(x) = self.duration { write!(f, ",DURATION={}", DecimalFloatingPoint::from_duration(x))?; @@ -58,13 +59,13 @@ impl fmt::Display for ExtXDateRange { )?; } if let Some(ref x) = self.scte35_cmd { - write!(f, ",SCTE35-CMD={}", x)?; + write!(f, ",SCTE35-CMD={}", quote(x))?; } if let Some(ref x) = self.scte35_out { - write!(f, ",SCTE35-OUT={}", x)?; + write!(f, ",SCTE35-OUT={}", quote(x))?; } if let Some(ref x) = self.scte35_in { - write!(f, ",SCTE35-IN={}", x)?; + write!(f, ",SCTE35-IN={}", quote(x))?; } if self.end_on_next { write!(f, ",END-ON-NEXT=YES",)?; @@ -96,10 +97,10 @@ impl FromStr for ExtXDateRange { for attr in attrs { let (key, value) = track!(attr)?; match key { - "ID" => id = Some(track!(value.parse())?), - "CLASS" => class = Some(track!(value.parse())?), - "START-DATE" => start_date = Some(track!(value.parse())?), - "END-DATE" => end_date = Some(track!(value.parse())?), + "ID" => id = Some(unquote(value)), + "CLASS" => class = Some(unquote(value)), + "START-DATE" => start_date = Some(unquote(value)), + "END-DATE" => end_date = Some(unquote(value)), "DURATION" => { let seconds: DecimalFloatingPoint = track!(value.parse())?; duration = Some(seconds.to_duration()); @@ -108,9 +109,9 @@ impl FromStr for ExtXDateRange { let seconds: DecimalFloatingPoint = track!(value.parse())?; planned_duration = Some(seconds.to_duration()); } - "SCTE35-CMD" => scte35_cmd = Some(track!(value.parse())?), - "SCTE35-OUT" => scte35_out = Some(track!(value.parse())?), - "SCTE35-IN" => scte35_in = Some(track!(value.parse())?), + "SCTE35-CMD" => scte35_cmd = Some(unquote(value)), + "SCTE35-OUT" => scte35_out = Some(unquote(value)), + "SCTE35-IN" => scte35_in = Some(unquote(value)), "END-ON-NEXT" => { track_assert_eq!(value, "YES", ErrorKind::InvalidInput); end_on_next = true; diff --git a/src/tags/media_segment/key.rs b/src/tags/media_segment/key.rs index e9e535c..9f2811f 100644 --- a/src/tags/media_segment/key.rs +++ b/src/tags/media_segment/key.rs @@ -77,7 +77,7 @@ impl FromStr for ExtXKey { #[cfg(test)] mod test { use super::*; - use crate::types::{EncryptionMethod, InitializationVector, QuotedString}; + use crate::types::{EncryptionMethod, InitializationVector}; #[test] fn ext_x_key() { @@ -89,7 +89,7 @@ mod test { let tag = ExtXKey::new(DecryptionKey { method: EncryptionMethod::Aes128, - uri: QuotedString::new("foo").unwrap(), + uri: "foo".to_string(), iv: None, key_format: None, key_format_versions: None, @@ -101,7 +101,7 @@ mod test { let tag = ExtXKey::new(DecryptionKey { method: EncryptionMethod::Aes128, - uri: QuotedString::new("foo").unwrap(), + uri: "foo".to_string(), iv: Some(InitializationVector([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ])), @@ -115,11 +115,11 @@ mod test { let tag = ExtXKey::new(DecryptionKey { method: EncryptionMethod::Aes128, - uri: QuotedString::new("foo").unwrap(), + uri: "foo".to_string(), iv: Some(InitializationVector([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ])), - key_format: Some(QuotedString::new("baz").unwrap()), + key_format: Some("baz".to_string()), key_format_versions: None, }); let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo",IV=0x000102030405060708090a0b0c0d0e0f,KEYFORMAT="baz""#; diff --git a/src/tags/media_segment/map.rs b/src/tags/media_segment/map.rs index 53c408c..96c45f8 100644 --- a/src/tags/media_segment/map.rs +++ b/src/tags/media_segment/map.rs @@ -1,5 +1,6 @@ use crate::attribute::AttributePairs; -use crate::types::{ByteRange, ProtocolVersion, QuotedString}; +use crate::types::{ByteRange, ProtocolVersion}; +use crate::utils::{quote, unquote}; use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; @@ -9,7 +10,7 @@ use std::str::FromStr; /// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5 #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExtXMap { - uri: QuotedString, + uri: String, range: Option, } @@ -17,20 +18,23 @@ impl ExtXMap { pub(crate) const PREFIX: &'static str = "#EXT-X-MAP:"; /// Makes a new `ExtXMap` tag. - pub fn new(uri: QuotedString) -> Self { - ExtXMap { uri, range: None } + pub fn new(uri: T) -> Self { + ExtXMap { + uri: uri.to_string(), + range: None, + } } /// Makes a new `ExtXMap` tag with the given range. - pub fn with_range(uri: QuotedString, range: ByteRange) -> Self { + pub fn with_range(uri: T, range: ByteRange) -> Self { ExtXMap { - uri, + uri: uri.to_string(), range: Some(range), } } /// Returns the URI that identifies a resource that contains the media initialization section. - pub fn uri(&self) -> &QuotedString { + pub fn uri(&self) -> &String { &self.uri } @@ -48,9 +52,9 @@ impl ExtXMap { impl fmt::Display for ExtXMap { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", Self::PREFIX)?; - write!(f, "URI={}", self.uri)?; + write!(f, "URI={}", quote(&self.uri))?; if let Some(ref x) = self.range { - write!(f, ",BYTERANGE=\"{}\"", x)?; + write!(f, ",BYTERANGE={}", quote(x))?; } Ok(()) } @@ -67,10 +71,9 @@ impl FromStr for ExtXMap { for attr in attrs { let (key, value) = track!(attr)?; match key { - "URI" => uri = Some(track!(value.parse())?), + "URI" => uri = Some(unquote(value)), "BYTERANGE" => { - let s: QuotedString = track!(value.parse())?; - range = Some(track!(s.parse())?); + range = Some(track!(unquote(value).parse())?); } _ => { // [6.3.1. General Client Responsibilities] @@ -90,14 +93,14 @@ mod test { #[test] fn ext_x_map() { - let tag = ExtXMap::new(QuotedString::new("foo").unwrap()); + let tag = ExtXMap::new("foo"); let text = r#"#EXT-X-MAP:URI="foo""#; assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V6); let tag = ExtXMap::with_range( - QuotedString::new("foo").unwrap(), + "foo", ByteRange { length: 9, start: Some(2), diff --git a/src/types/closed_captions.rs b/src/types/closed_captions.rs index fb4ed85..7f51d9f 100644 --- a/src/types/closed_captions.rs +++ b/src/types/closed_captions.rs @@ -1,4 +1,4 @@ -use crate::types::QuotedString; +use crate::utils::{quote, unquote}; use crate::{Error, Result}; use std::fmt; use std::str::{self, FromStr}; @@ -11,14 +11,14 @@ use std::str::{self, FromStr}; #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ClosedCaptions { - GroupId(QuotedString), + GroupId(String), None, } impl fmt::Display for ClosedCaptions { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - ClosedCaptions::GroupId(ref x) => x.fmt(f), + ClosedCaptions::GroupId(ref x) => write!(f, "{}", quote(x)), ClosedCaptions::None => "NONE".fmt(f), } } @@ -30,7 +30,7 @@ impl FromStr for ClosedCaptions { if s == "NONE" { Ok(ClosedCaptions::None) } else { - Ok(ClosedCaptions::GroupId(track!(s.parse())?)) + Ok(ClosedCaptions::GroupId(unquote(s))) } } } @@ -44,7 +44,7 @@ mod tests { let closed_captions = ClosedCaptions::None; assert_eq!(closed_captions.to_string(), "NONE".to_string()); - let closed_captions = ClosedCaptions::GroupId(QuotedString::new("value").unwrap()); + let closed_captions = ClosedCaptions::GroupId("value".into()); assert_eq!(closed_captions.to_string(), "\"value\"".to_string()); } @@ -53,7 +53,7 @@ mod tests { let closed_captions = ClosedCaptions::None; assert_eq!(closed_captions, "NONE".parse::().unwrap()); - let closed_captions = ClosedCaptions::GroupId(QuotedString::new("value").unwrap()); + let closed_captions = ClosedCaptions::GroupId("value".into()); assert_eq!( closed_captions, "\"value\"".parse::().unwrap() diff --git a/src/types/decryption_key.rs b/src/types/decryption_key.rs index ac57237..15a6f35 100644 --- a/src/types/decryption_key.rs +++ b/src/types/decryption_key.rs @@ -1,5 +1,6 @@ use crate::attribute::AttributePairs; -use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion, QuotedString}; +use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion}; +use crate::utils::{quote, unquote}; use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::{self, FromStr}; @@ -13,10 +14,10 @@ use std::str::{self, FromStr}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct DecryptionKey { pub method: EncryptionMethod, - pub uri: QuotedString, + pub uri: String, pub iv: Option, - pub key_format: Option, - pub key_format_versions: Option, + pub key_format: Option, + pub key_format_versions: Option, } impl DecryptionKey { @@ -34,15 +35,15 @@ impl DecryptionKey { impl fmt::Display for DecryptionKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "METHOD={}", self.method)?; - write!(f, ",URI={}", self.uri)?; + write!(f, ",URI={}", quote(&self.uri))?; if let Some(ref x) = self.iv { write!(f, ",IV={}", x)?; } if let Some(ref x) = self.key_format { - write!(f, ",KEYFORMAT={}", x)?; + write!(f, ",KEYFORMAT={}", quote(x))?; } if let Some(ref x) = self.key_format_versions { - write!(f, ",KEYFORMATVERSIONS={}", x)?; + write!(f, ",KEYFORMATVERSIONS={}", quote(x))?; } Ok(()) } @@ -61,10 +62,10 @@ impl FromStr for DecryptionKey { let (key, value) = track!(attr)?; match key { "METHOD" => method = Some(track!(value.parse())?), - "URI" => uri = Some(track!(value.parse())?), + "URI" => uri = Some(unquote(value)), "IV" => iv = Some(track!(value.parse())?), - "KEYFORMAT" => key_format = Some(track!(value.parse())?), - "KEYFORMATVERSIONS" => key_format_versions = Some(track!(value.parse())?), + "KEYFORMAT" => key_format = Some(unquote(value)), + "KEYFORMATVERSIONS" => key_format_versions = Some(unquote(value)), _ => { // [6.3.1. General Client Responsibilities] // > ignore any attribute/value pair with an unrecognized AttributeName. diff --git a/src/types/mod.rs b/src/types/mod.rs index d3f30d2..1b1830d 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -12,7 +12,6 @@ mod initialization_vector; mod media_type; mod playlist_type; mod protocol_version; -mod quoted_string; mod session_data; mod signed_decimal_floating_point; mod single_line_string; @@ -30,7 +29,6 @@ pub use initialization_vector::*; pub use media_type::*; pub use playlist_type::*; pub use protocol_version::*; -pub use quoted_string::*; pub use session_data::*; pub use signed_decimal_floating_point::*; pub use single_line_string::*; diff --git a/src/types/quoted_string.rs b/src/types/quoted_string.rs deleted file mode 100644 index 07c6118..0000000 --- a/src/types/quoted_string.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::{Error, ErrorKind, Result}; -use std::fmt; -use std::ops::Deref; -use std::str::{self, FromStr}; - -/// Quoted string. -/// -/// See: [4.2. Attribute Lists] -/// -/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2 -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct QuotedString(String); - -impl QuotedString { - /// Makes a new `QuotedString` instance. - /// - /// # Errors - /// - /// If the given string contains any control characters or double-quote character, - /// this function will return an error which has the kind `ErrorKind::InvalidInput`. - pub fn new>(s: T) -> Result { - let s = s.into(); - track_assert!( - !s.chars().any(|c| c.is_control() || c == '"'), - ErrorKind::InvalidInput - ); - Ok(QuotedString(s)) - } -} - -impl Deref for QuotedString { - type Target = str; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsRef for QuotedString { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl fmt::Display for QuotedString { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self.0) - } -} - -impl FromStr for QuotedString { - type Err = Error; - fn from_str(s: &str) -> Result { - let len = s.len(); - let bytes = s.as_bytes(); - track_assert!(len >= 2, ErrorKind::InvalidInput); - track_assert_eq!(bytes[0], b'"', ErrorKind::InvalidInput); - track_assert_eq!(bytes[len - 1], b'"', ErrorKind::InvalidInput); - - let s = unsafe { str::from_utf8_unchecked(&bytes[1..len - 1]) }; - track!(QuotedString::new(s)) - } -} diff --git a/src/types/session_data.rs b/src/types/session_data.rs index 25c90e1..4875b35 100644 --- a/src/types/session_data.rs +++ b/src/types/session_data.rs @@ -1,5 +1,3 @@ -use crate::types::QuotedString; - /// Session data. /// /// See: [4.3.4.4. EXT-X-SESSION-DATA] @@ -8,6 +6,6 @@ use crate::types::QuotedString; #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum SessionData { - Value(QuotedString), - Uri(QuotedString), + Value(String), + Uri(String), } diff --git a/src/utils.rs b/src/utils.rs index b313c66..605eb1d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,15 +1,77 @@ use crate::{ErrorKind, Result}; use trackable::error::ErrorKindExt; -pub fn parse_yes_or_no(s: &str) -> Result { - match s { +pub(crate) fn parse_yes_or_no>(s: T) -> Result { + match s.as_ref() { "YES" => Ok(true), "NO" => Ok(false), - _ => track_panic!(ErrorKind::InvalidInput, "Unexpected value: {:?}", s), + _ => track_panic!( + ErrorKind::InvalidInput, + "Unexpected value: {:?}", + s.as_ref() + ), } } -pub fn parse_u64(s: &str) -> Result { - let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?; +pub(crate) fn parse_u64>(s: T) -> Result { + let n = track!(s + .as_ref() + .parse() + .map_err(|e| ErrorKind::InvalidInput.cause(e)))?; Ok(n) } + +/// According to the documentation the following characters are forbidden +/// inside a quoted string: +/// - carriage return (`\r`) +/// - new line (`\n`) +/// - double quotes (`"`) +/// +/// Therefore it is safe to simply remove any occurence of those characters. +/// [rfc8216#section-4.2](https://tools.ietf.org/html/rfc8216#section-4.2) +pub(crate) fn unquote(value: T) -> String { + value + .to_string() + .replace("\"", "") + .replace("\n", "") + .replace("\r", "") +} + +/// Puts a string inside quotes. +pub(crate) fn quote(value: T) -> String { + // the replace is for the case, that quote is called on an already quoted string, which could + // cause problems! + format!("\"{}\"", value.to_string().replace("\"", "")) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_yes_or_no() { + assert!(parse_yes_or_no("YES").unwrap()); + assert!(!parse_yes_or_no("NO").unwrap()); + // TODO: test for error + } + + #[test] + fn test_parse_u64() { + assert_eq!(parse_u64("1").unwrap(), 1); + assert_eq!(parse_u64("25").unwrap(), 25); + // TODO: test for error + } + + #[test] + fn test_unquote() { + assert_eq!(unquote("\"TestValue\""), "TestValue".to_string()); + assert_eq!(unquote("\"TestValue\n\""), "TestValue".to_string()); + assert_eq!(unquote("\"TestValue\n\r\""), "TestValue".to_string()); + } + + #[test] + fn test_quote() { + assert_eq!(quote("value"), "\"value\"".to_string()); + assert_eq!(quote("\"value\""), "\"value\"".to_string()); + } +}