1use 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 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 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
206fn 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
213fn 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}