SessionData: Must have either VALUE or URI, but not both.

If SessionData has a value, don't write the URI and vice-versa.

As per https://tools.ietf.org/html/rfc8216#section-4.3.4.4
EXT-X-SESSION-DATA must have one or the other, not both.
This commit is contained in:
Jan Schmidt 2021-04-20 02:35:45 +10:00
parent 85b0826103
commit 870ca830d3
3 changed files with 49 additions and 22 deletions

View file

@ -319,8 +319,9 @@ named!(pub alternative_media_tag<AlternativeMedia>,
);
named!(pub session_data_tag<SessionData>,
do_parse!( tag!("#EXT-X-SESSION-DATA:") >> attributes: key_value_pairs >>
( SessionData::from_hashmap(attributes)))
do_parse!( tag!("#EXT-X-SESSION-DATA:") >>
session_data: map_res!(key_value_pairs, |attrs| SessionData::from_hashmap(attrs)) >>
( session_data))
);
named!(pub session_key_tag<SessionKey>,

View file

@ -343,6 +343,9 @@ impl fmt::Display for AlternativeMediaType {
/// [`#EXT-X-SESSION-KEY:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.5)
/// The EXT-X-SESSION-KEY tag allows encryption keys from Media Playlists
/// to be specified in a Master Playlist. This allows the client to
/// preload these keys without having to read the Media Playlist(s) first.
#[derive(Debug, Default, PartialEq, Clone)]
pub struct SessionKey(pub Key);
@ -354,34 +357,56 @@ impl SessionKey {
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum SessionDataField {
Value(String),
Uri(String)
}
/// [`#EXT-X-SESSION-DATA:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.4)
/// The EXT-X-SESSION-KEY tag allows encryption keys from Media Playlists
/// to be specified in a Master Playlist. This allows the client to
/// preload these keys without having to read the Media Playlist(s) first.
#[derive(Debug, Default, PartialEq, Clone)]
/// The EXT-X-SESSION-DATA tag allows arbitrary session data to be carried
/// in a Master Playlist.
#[derive(Debug, PartialEq, Clone)]
pub struct SessionData {
pub data_id: String,
pub value: String,
pub uri: String,
pub field: SessionDataField,
pub language: Option<String>,
}
impl SessionData {
pub fn from_hashmap(mut attrs: HashMap<String, String>) -> SessionData {
SessionData {
data_id: attrs.remove("DATA-ID").unwrap_or_else(String::new),
value: attrs.remove("VALUE").unwrap_or_else(String::new),
uri: attrs.remove("URI").unwrap_or_else(String::new),
pub fn from_hashmap(mut attrs: HashMap<String, String>) -> Result<SessionData, String> {
let data_id = match attrs.remove("DATA-ID") {
Some(data_id) => data_id,
None => return Err("EXT-X-SESSION-DATA field without DATA-ID".to_string())
};
let value = attrs.remove("VALUE");
let uri = attrs.remove("URI");
// SessionData must contain either a VALUE or a URI,
// but not both https://tools.ietf.org/html/rfc8216#section-4.3.4.4
let field = match (value, uri) {
(Some(value), None) => SessionDataField::Value(value),
(None, Some(uri)) => SessionDataField::Uri(uri),
(Some(_), Some(_)) => return Err(format!["EXT-X-SESSION-DATA tag {} contains both a value and a uri", data_id]),
(None, None) => return Err(format!["EXT-X-SESSION-DATA tag {} must contain either a value or a uri", data_id]),
};
Ok(SessionData {
data_id,
field,
language: attrs.remove("LANGUAGE"),
}
})
}
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
write!(w, "#EXT-X-SESSION-DATA:")?;
write!(w, "DATA-ID=\"{}\"", self.data_id)?;
write!(w, ",VALUE=\"{}\"", self.value)?;
write!(w, ",URI=\"{}\"", self.uri)?;
match &self.field {
SessionDataField::Value(value) => write!(w, ",VALUE=\"{}\"", value)?,
SessionDataField::Uri(uri) => write!(w, ",URI=\"{}\"", uri)?,
};
write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?;
write!(w, "\n")
}

View file

@ -333,12 +333,13 @@ fn create_and_parse_master_playlist_full() {
closed_captions: Some("closed_captions".into()),
}
],
session_data: Some(SessionData {
data_id: "****".into(),
value: "%%%%".into(),
uri: "++++".into(),
language: Some("SessionDataLanguage".into()),
}),
session_data: Some(
SessionData {
data_id: "****".into(),
field: SessionDataField::Value("%%%%".to_string()),
language: Some("SessionDataLanguage".into()),
}
),
session_key: Some(SessionKey(Key {
method: "AES-128".into(),
uri: Some("https://secure.domain.com".into()),