diff options
Diffstat (limited to 'components/script/dom/mediafragmentparser.rs')
-rw-r--r-- | components/script/dom/mediafragmentparser.rs | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/components/script/dom/mediafragmentparser.rs b/components/script/dom/mediafragmentparser.rs new file mode 100644 index 00000000000..2e9de4b0f8c --- /dev/null +++ b/components/script/dom/mediafragmentparser.rs @@ -0,0 +1,340 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use chrono::NaiveDateTime; +use servo_url::ServoUrl; +use std::borrow::Cow; +use std::collections::VecDeque; +use std::str::FromStr; +use url::{form_urlencoded, Position, Url}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum SpatialRegion { + Pixel, + Percent, +} + +impl FromStr for SpatialRegion { + type Err = (); + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "pixel" => Ok(SpatialRegion::Pixel), + "percent" => Ok(SpatialRegion::Percent), + _ => Err(()), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SpatialClipping { + region: Option<SpatialRegion>, + x: u32, + y: u32, + width: u32, + height: u32, +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct MediaFragmentParser { + id: Option<String>, + tracks: Vec<String>, + spatial: Option<SpatialClipping>, + start: Option<f64>, + end: Option<f64>, +} + +impl MediaFragmentParser { + pub fn id(&self) -> Option<String> { + self.id.clone() + } + + pub fn tracks(&self) -> &Vec<String> { + self.tracks.as_ref() + } + + pub fn start(&self) -> Option<f64> { + self.start + } + + // Parse an str of key value pairs, a URL, or a fragment. + pub fn parse(input: &str) -> MediaFragmentParser { + let mut parser = MediaFragmentParser::default(); + let (query, fragment) = split_url(input); + let mut octets = decode_octets(query.as_bytes()); + octets.extend(decode_octets(fragment.as_bytes())); + + if !octets.is_empty() { + for (key, value) in octets.iter() { + match key.as_bytes() { + b"t" => { + if let Ok((start, end)) = parser.parse_temporal(value) { + parser.start = start; + parser.end = end; + } + }, + b"xywh" => { + if let Ok(spatial) = parser.parse_spatial(value) { + parser.spatial = Some(spatial); + } + }, + b"id" => parser.id = Some(value.to_string()), + b"track" => parser.tracks.push(value.to_string()), + _ => {}, + } + } + parser + } else { + if let Ok((start, end)) = parser.parse_temporal(input) { + parser.start = start; + parser.end = end; + } else if let Ok(spatial) = parser.parse_spatial(input) { + parser.spatial = Some(spatial); + } + parser + } + } + + // Either NPT or UTC timestamp (real world clock time). + fn parse_temporal(&self, input: &str) -> Result<(Option<f64>, Option<f64>), ()> { + let (_, fragment) = split_prefix(input); + + if fragment.ends_with('Z') || fragment.ends_with("Z-") { + return self.parse_utc_timestamp(fragment); + } + + if fragment.starts_with(',') || !fragment.contains(',') { + let sec = parse_hms(&fragment.replace(',', ""))?; + if fragment.starts_with(',') { + Ok((Some(0.), Some(sec))) + } else { + Ok((Some(sec), None)) + } + } else { + let mut iterator = fragment.split(','); + let start = parse_hms(iterator.next().ok_or_else(|| ())?)?; + let end = parse_hms(iterator.next().ok_or_else(|| ())?)?; + + if iterator.next().is_some() || start >= end { + return Err(()); + } + + Ok((Some(start), Some(end))) + } + } + + fn parse_utc_timestamp(&self, input: &str) -> Result<(Option<f64>, Option<f64>), ()> { + if input.ends_with('-') || input.starts_with(',') || !input.contains('-') { + let sec = parse_hms( + NaiveDateTime::parse_from_str( + &input.replace('-', "").replace(',', ""), + "%Y%m%dT%H%M%S%.fZ", + ) + .map_err(|_| ())? + .time() + .to_string() + .as_ref(), + )?; + if input.starts_with(',') { + Ok((Some(0.), Some(sec))) + } else { + Ok((Some(sec), None)) + } + } else { + let vec: Vec<&str> = input.split('-').collect(); + let mut hms: Vec<f64> = vec + .iter() + .map(|s| NaiveDateTime::parse_from_str(s, "%Y%m%dT%H%M%S%.fZ")) + .flatten() + .map(|s| parse_hms(&s.time().to_string())) + .flatten() + .collect(); + + let end = hms.pop().ok_or_else(|| ())?; + let start = hms.pop().ok_or_else(|| ())?; + + if !hms.is_empty() || start >= end { + return Err(()); + } + + Ok((Some(start), Some(end))) + } + } + + fn parse_spatial(&self, input: &str) -> Result<SpatialClipping, ()> { + let (prefix, s) = split_prefix(input); + let vec: Vec<&str> = s.split(',').collect(); + let mut queue: VecDeque<u32> = vec.iter().map(|s| s.parse::<u32>()).flatten().collect(); + + let mut clipping = SpatialClipping { + region: None, + x: queue.pop_front().ok_or_else(|| ())?, + y: queue.pop_front().ok_or_else(|| ())?, + width: queue.pop_front().ok_or_else(|| ())?, + height: queue.pop_front().ok_or_else(|| ())?, + }; + + if !queue.is_empty() { + return Err(()); + } + + if let Some(s) = prefix { + let region = SpatialRegion::from_str(s)?; + if region.eq(&SpatialRegion::Percent) && + (clipping.x + clipping.width > 100 || clipping.y + clipping.height > 100) + { + return Err(()); + } + clipping.region = Some(region); + } + + Ok(clipping) + } +} + +impl From<&Url> for MediaFragmentParser { + fn from(url: &Url) -> Self { + let input: &str = &url[Position::AfterPath..]; + MediaFragmentParser::parse(input) + } +} + +impl From<&ServoUrl> for MediaFragmentParser { + fn from(servo_url: &ServoUrl) -> Self { + let input: &str = &servo_url[Position::AfterPath..]; + MediaFragmentParser::parse(input) + } +} + +// 5.1.1 Processing name-value components. +fn decode_octets(bytes: &[u8]) -> Vec<(Cow<str>, Cow<str>)> { + form_urlencoded::parse(bytes) + .filter(|(key, _)| match key.as_bytes() { + b"t" | b"track" | b"id" | b"xywh" => true, + _ => false, + }) + .collect() +} + +// Parse a full URL or a relative URL without a base retaining the query and/or fragment. +fn split_url(s: &str) -> (&str, &str) { + if s.contains('?') || s.contains('#') { + for (index, byte) in s.bytes().enumerate() { + if byte == b'?' { + let partial = &s[index + 1..]; + for (i, byte) in partial.bytes().enumerate() { + if byte == b'#' { + return (&partial[..i], &partial[i + 1..]); + } + } + return (partial, ""); + } + + if byte == b'#' { + return ("", &s[index + 1..]); + } + } + } + ("", s) +} + +fn is_byte_number(byte: u8) -> bool { + match byte { + 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 => true, + _ => false, + } +} + +fn split_prefix(s: &str) -> (Option<&str>, &str) { + for (index, byte) in s.bytes().enumerate() { + if index == 0 && is_byte_number(byte) { + break; + } + + if byte == b':' { + return (Some(&s[..index]), &s[index + 1..]); + } + } + (None, s) +} + +fn hms_to_seconds(hour: u32, minutes: u32, seconds: f64) -> f64 { + let mut sec: f64 = f64::from(hour) * 3600.; + sec += f64::from(minutes) * 60.; + sec += seconds; + sec +} + +fn parse_npt_minute(s: &str) -> Result<u32, ()> { + if s.len() > 2 { + return Err(()); + } + + let minute = s.parse().map_err(|_| ())?; + if minute > 59 { + return Err(()); + } + + Ok(minute) +} + +fn parse_npt_seconds(s: &str) -> Result<f64, ()> { + if s.contains('.') { + let mut iterator = s.split('.'); + if let Some(s) = iterator.next() { + if s.len() > 2 { + return Err(()); + } + let sec = s.parse::<u32>().map_err(|_| ())?; + if sec > 59 { + return Err(()); + } + } + + let _ = iterator.next(); + if iterator.next().is_some() { + return Err(()); + } + } + + s.parse().map_err(|_| ()) +} + +fn parse_hms(s: &str) -> Result<f64, ()> { + let mut vec: VecDeque<&str> = s.split(':').collect(); + vec.retain(|x| !x.eq(&"")); + + let result = match vec.len() { + 1 => { + let secs = vec + .pop_front() + .ok_or_else(|| ())? + .parse::<f64>() + .map_err(|_| ())?; + + if secs == 0. { + return Err(()); + } + + hms_to_seconds(0, 0, secs) + }, + 2 => hms_to_seconds( + 0, + parse_npt_minute(vec.pop_front().ok_or_else(|| ())?)?, + parse_npt_seconds(vec.pop_front().ok_or_else(|| ())?)?, + ), + 3 => hms_to_seconds( + vec.pop_front().ok_or_else(|| ())?.parse().map_err(|_| ())?, + parse_npt_minute(vec.pop_front().ok_or_else(|| ())?)?, + parse_npt_seconds(vec.pop_front().ok_or_else(|| ())?)?, + ), + _ => return Err(()), + }; + + if !vec.is_empty() { + return Err(()); + } + + Ok(result) +} |