script/dom/
mediafragmentparser.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::borrow::Cow;
6use std::collections::VecDeque;
7use std::str::FromStr;
8
9use chrono::NaiveDateTime;
10use servo_url::ServoUrl;
11use url::{Position, Url, form_urlencoded};
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub(crate) enum SpatialRegion {
15    Pixel,
16    Percent,
17}
18
19impl FromStr for SpatialRegion {
20    type Err = ();
21
22    fn from_str(s: &str) -> Result<Self, Self::Err> {
23        match s {
24            "pixel" => Ok(SpatialRegion::Pixel),
25            "percent" => Ok(SpatialRegion::Percent),
26            _ => Err(()),
27        }
28    }
29}
30
31#[derive(Clone, Debug, Eq, PartialEq)]
32pub(crate) struct SpatialClipping {
33    region: Option<SpatialRegion>,
34    x: u32,
35    y: u32,
36    width: u32,
37    height: u32,
38}
39
40#[derive(Clone, Debug, Default, PartialEq)]
41pub(crate) struct MediaFragmentParser {
42    id: Option<String>,
43    tracks: Vec<String>,
44    spatial: Option<SpatialClipping>,
45    start: Option<f64>,
46    end: Option<f64>,
47}
48
49impl MediaFragmentParser {
50    pub(crate) fn id(&self) -> Option<String> {
51        self.id.clone()
52    }
53
54    pub(crate) fn tracks(&self) -> &Vec<String> {
55        self.tracks.as_ref()
56    }
57
58    pub(crate) fn start(&self) -> Option<f64> {
59        self.start
60    }
61
62    // Parse an str of key value pairs, a URL, or a fragment.
63    pub(crate) fn parse(input: &str) -> MediaFragmentParser {
64        let mut parser = MediaFragmentParser::default();
65        let (query, fragment) = split_url(input);
66        let mut octets = decode_octets(query.as_bytes());
67        octets.extend(decode_octets(fragment.as_bytes()));
68
69        if !octets.is_empty() {
70            for (key, value) in octets.iter() {
71                match key.as_bytes() {
72                    b"t" => {
73                        if let Ok((start, end)) = parser.parse_temporal(value) {
74                            parser.start = start;
75                            parser.end = end;
76                        }
77                    },
78                    b"xywh" => {
79                        if let Ok(spatial) = parser.parse_spatial(value) {
80                            parser.spatial = Some(spatial);
81                        }
82                    },
83                    b"id" => parser.id = Some(value.to_string()),
84                    b"track" => parser.tracks.push(value.to_string()),
85                    _ => {},
86                }
87            }
88            parser
89        } else {
90            if let Ok((start, end)) = parser.parse_temporal(input) {
91                parser.start = start;
92                parser.end = end;
93            } else if let Ok(spatial) = parser.parse_spatial(input) {
94                parser.spatial = Some(spatial);
95            }
96            parser
97        }
98    }
99
100    // Either NPT or UTC timestamp (real world clock time).
101    fn parse_temporal(&self, input: &str) -> Result<(Option<f64>, Option<f64>), ()> {
102        let (_, fragment) = split_prefix(input);
103
104        if fragment.ends_with('Z') || fragment.ends_with("Z-") {
105            return self.parse_utc_timestamp(fragment);
106        }
107
108        if fragment.starts_with(',') || !fragment.contains(',') {
109            let sec = parse_hms(&fragment.replace(',', ""))?;
110            if fragment.starts_with(',') {
111                Ok((Some(0.), Some(sec)))
112            } else {
113                Ok((Some(sec), None))
114            }
115        } else {
116            let mut iterator = fragment.split(',');
117            let start = parse_hms(iterator.next().ok_or(())?)?;
118            let end = parse_hms(iterator.next().ok_or(())?)?;
119
120            if iterator.next().is_some() || start >= end {
121                return Err(());
122            }
123
124            Ok((Some(start), Some(end)))
125        }
126    }
127
128    fn parse_utc_timestamp(&self, input: &str) -> Result<(Option<f64>, Option<f64>), ()> {
129        if input.ends_with('-') || input.starts_with(',') || !input.contains('-') {
130            let sec = parse_hms(
131                NaiveDateTime::parse_from_str(&input.replace(['-', ','], ""), "%Y%m%dT%H%M%S%.fZ")
132                    .map_err(|_| ())?
133                    .time()
134                    .to_string()
135                    .as_ref(),
136            )?;
137            if input.starts_with(',') {
138                Ok((Some(0.), Some(sec)))
139            } else {
140                Ok((Some(sec), None))
141            }
142        } else {
143            let vec: Vec<&str> = input.split('-').collect();
144            let mut hms: Vec<f64> = vec
145                .iter()
146                .flat_map(|s| NaiveDateTime::parse_from_str(s, "%Y%m%dT%H%M%S%.fZ"))
147                .flat_map(|s| parse_hms(&s.time().to_string()))
148                .collect();
149
150            let end = hms.pop().ok_or(())?;
151            let start = hms.pop().ok_or(())?;
152
153            if !hms.is_empty() || start >= end {
154                return Err(());
155            }
156
157            Ok((Some(start), Some(end)))
158        }
159    }
160
161    fn parse_spatial(&self, input: &str) -> Result<SpatialClipping, ()> {
162        let (prefix, s) = split_prefix(input);
163        let vec: Vec<&str> = s.split(',').collect();
164        let mut queue: VecDeque<u32> = vec.iter().flat_map(|s| s.parse::<u32>()).collect();
165
166        let mut clipping = SpatialClipping {
167            region: None,
168            x: queue.pop_front().ok_or(())?,
169            y: queue.pop_front().ok_or(())?,
170            width: queue.pop_front().ok_or(())?,
171            height: queue.pop_front().ok_or(())?,
172        };
173
174        if !queue.is_empty() {
175            return Err(());
176        }
177
178        if let Some(s) = prefix {
179            let region = SpatialRegion::from_str(s)?;
180            if region.eq(&SpatialRegion::Percent) &&
181                (clipping.x + clipping.width > 100 || clipping.y + clipping.height > 100)
182            {
183                return Err(());
184            }
185            clipping.region = Some(region);
186        }
187
188        Ok(clipping)
189    }
190}
191
192impl From<&Url> for MediaFragmentParser {
193    fn from(url: &Url) -> Self {
194        let input: &str = &url[Position::AfterPath..];
195        MediaFragmentParser::parse(input)
196    }
197}
198
199impl From<&ServoUrl> for MediaFragmentParser {
200    fn from(servo_url: &ServoUrl) -> Self {
201        let input: &str = &servo_url[Position::AfterPath..];
202        MediaFragmentParser::parse(input)
203    }
204}
205
206// 5.1.1 Processing name-value components.
207fn decode_octets(bytes: &[u8]) -> Vec<(Cow<'_, str>, Cow<'_, str>)> {
208    form_urlencoded::parse(bytes)
209        .filter(|(key, _)| matches!(key.as_bytes(), b"t" | b"track" | b"id" | b"xywh"))
210        .collect()
211}
212
213// Parse a full URL or a relative URL without a base retaining the query and/or fragment.
214fn split_url(s: &str) -> (&str, &str) {
215    if s.contains('?') || s.contains('#') {
216        for (index, byte) in s.bytes().enumerate() {
217            if byte == b'?' {
218                let partial = &s[index + 1..];
219                for (i, byte) in partial.bytes().enumerate() {
220                    if byte == b'#' {
221                        return (&partial[..i], &partial[i + 1..]);
222                    }
223                }
224                return (partial, "");
225            }
226
227            if byte == b'#' {
228                return ("", &s[index + 1..]);
229            }
230        }
231    }
232    ("", s)
233}
234
235fn is_byte_number(byte: u8) -> bool {
236    matches!(byte, 48..=57)
237}
238
239fn split_prefix(s: &str) -> (Option<&str>, &str) {
240    for (index, byte) in s.bytes().enumerate() {
241        if index == 0 && is_byte_number(byte) {
242            break;
243        }
244
245        if byte == b':' {
246            return (Some(&s[..index]), &s[index + 1..]);
247        }
248    }
249    (None, s)
250}
251
252fn hms_to_seconds(hour: u32, minutes: u32, seconds: f64) -> f64 {
253    let mut sec: f64 = f64::from(hour) * 3600.;
254    sec += f64::from(minutes) * 60.;
255    sec += seconds;
256    sec
257}
258
259fn parse_npt_minute(s: &str) -> Result<u32, ()> {
260    if s.len() > 2 {
261        return Err(());
262    }
263
264    let minute = s.parse().map_err(|_| ())?;
265    if minute > 59 {
266        return Err(());
267    }
268
269    Ok(minute)
270}
271
272fn parse_npt_seconds(s: &str) -> Result<f64, ()> {
273    if s.contains('.') {
274        let mut iterator = s.split('.');
275        if let Some(s) = iterator.next() {
276            if s.len() > 2 {
277                return Err(());
278            }
279            let sec = s.parse::<u32>().map_err(|_| ())?;
280            if sec > 59 {
281                return Err(());
282            }
283        }
284
285        let _ = iterator.next();
286        if iterator.next().is_some() {
287            return Err(());
288        }
289    }
290
291    s.parse().map_err(|_| ())
292}
293
294fn parse_hms(s: &str) -> Result<f64, ()> {
295    let mut vec: VecDeque<&str> = s.split(':').collect();
296    vec.retain(|x| !x.is_empty());
297
298    let result = match vec.len() {
299        1 => {
300            let secs = vec.pop_front().ok_or(())?.parse::<f64>().map_err(|_| ())?;
301
302            if secs == 0. {
303                return Err(());
304            }
305
306            hms_to_seconds(0, 0, secs)
307        },
308        2 => hms_to_seconds(
309            0,
310            parse_npt_minute(vec.pop_front().ok_or(())?)?,
311            parse_npt_seconds(vec.pop_front().ok_or(())?)?,
312        ),
313        3 => hms_to_seconds(
314            vec.pop_front().ok_or(())?.parse().map_err(|_| ())?,
315            parse_npt_minute(vec.pop_front().ok_or(())?)?,
316            parse_npt_seconds(vec.pop_front().ok_or(())?)?,
317        ),
318        _ => return Err(()),
319    };
320
321    if !vec.is_empty() {
322        return Err(());
323    }
324
325    Ok(result)
326}