[go: nahoru, domu]

blob: e70183ac090d7149babdf3dfdbc6c76ca65d8cdf [file] [log] [blame]
Avi Drissmand387f0922022-09-14 20:51:311// Copyright 2021 The Chromium Authors
Will Cassella42480ee2022-01-07 21:56:112// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "media/formats/hls/tags.h"
6
7#include <cstddef>
Will Cassellaf28a25262022-01-26 20:24:318#include <type_traits>
9#include <utility>
Will Cassella9af5f782022-09-08 21:10:3610
Ted Meyer54a32922024-06-14 21:51:1611#include "base/logging.h"
Will Cassellaf28a25262022-01-26 20:24:3112#include "base/notreached.h"
Will Cassella23cdc9a12022-06-15 23:03:2613#include "base/time/time.h"
Will Cassella9af5f782022-09-08 21:10:3614#include "media/base/mime_util.h"
Will Cassellad9d31172022-03-04 20:52:2515#include "media/formats/hls/items.h"
Will Cassella42480ee2022-01-07 21:56:1116#include "media/formats/hls/parse_status.h"
Will Cassella58c7fc632022-04-06 18:48:3917#include "media/formats/hls/variable_dictionary.h"
Will Cassella42480ee2022-01-07 21:56:1118
Will Cassellad9d31172022-03-04 20:52:2519namespace media::hls {
Will Cassella42480ee2022-01-07 21:56:1120
Will Cassella058de252022-01-22 01:38:2021namespace {
22
23template <typename T>
24ParseStatus::Or<T> ParseEmptyTag(TagItem tag) {
Will Cassellaee56fa572022-04-12 19:17:2825 DCHECK(tag.GetName() == ToTagName(T::kName));
26 if (tag.GetContent().has_value()) {
Will Cassella42480ee2022-01-07 21:56:1127 return ParseStatusCode::kMalformedTag;
28 }
29
Will Cassella058de252022-01-22 01:38:2030 return T{};
31}
32
Will Cassellad7bc2c02022-05-11 00:52:0233template <typename T>
34ParseStatus::Or<T> ParseDecimalIntegerTag(TagItem tag,
35 types::DecimalInteger T::*field) {
36 DCHECK(tag.GetName() == ToTagName(T::kName));
37 if (!tag.GetContent().has_value()) {
38 return ParseStatusCode::kMalformedTag;
39 }
40
Will Cassella0d3ed6a2022-06-27 19:39:2941 auto value =
42 types::ParseDecimalInteger(tag.GetContent()->SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:2543 if (!value.has_value()) {
Will Cassellad7bc2c02022-05-11 00:52:0244 return ParseStatus(ParseStatusCode::kMalformedTag)
45 .AddCause(std::move(value).error());
46 }
47
48 T out;
49 out.*field = std::move(value).value();
50 return out;
51}
52
Ted Meyere55bc2b2024-02-24 00:31:2553template <typename T>
54ParseStatus::Or<T> ParseISO8601DateTimeTag(TagItem tag, base::Time T::*field) {
55 CHECK(tag.GetName() == ToTagName(T::kName));
56 if (!tag.GetContent().has_value()) {
57 return ParseStatusCode::kMalformedTag;
58 }
59 const auto content = tag.GetContent()->SkipVariableSubstitution().Str();
60 std::string content_nullterm = std::string(content);
61 T out;
62 base::Time time;
63 if (base::Time::FromString(content_nullterm.c_str(), &time)) {
64 out.*field = time;
65 } else {
66 return ParseStatusCode::kMalformedTag;
67 }
68 return out;
69}
70
Will Cassellaf28a25262022-01-26 20:24:3171// Attributes expected in `EXT-X-DEFINE` tag contents.
72// These must remain sorted alphabetically.
73enum class XDefineTagAttribute {
74 kImport,
75 kName,
76 kValue,
77 kMaxValue = kValue,
78};
79
Ted Meyerda164052024-03-04 05:46:2480constexpr std::string_view GetAttributeName(XDefineTagAttribute attribute) {
Will Cassellaf28a25262022-01-26 20:24:3181 switch (attribute) {
82 case XDefineTagAttribute::kImport:
83 return "IMPORT";
84 case XDefineTagAttribute::kName:
85 return "NAME";
86 case XDefineTagAttribute::kValue:
87 return "VALUE";
88 }
89
Peter Boström0ce6af602023-05-15 21:27:2490 NOTREACHED_NORETURN();
Will Cassellaf28a25262022-01-26 20:24:3191}
92
Will Cassella2f4c3202022-08-11 00:20:1993// Attributes expected in `EXT-X-MEDIA` tag contents.
94// These must remain sorted alphabetically.
95enum class XMediaTagAttribute {
96 kAssocLanguage,
97 kAutoselect,
98 kChannels,
99 kCharacteristics,
100 kDefault,
101 kForced,
102 kGroupId,
103 kInstreamId,
104 kLanguage,
105 kName,
106 kStableRenditionId,
107 kType,
108 kUri,
109 kMaxValue = kUri,
110};
111
Ted Meyerda164052024-03-04 05:46:24112constexpr std::string_view GetAttributeName(XMediaTagAttribute attribute) {
Will Cassella2f4c3202022-08-11 00:20:19113 switch (attribute) {
114 case XMediaTagAttribute::kAssocLanguage:
115 return "ASSOC-LANGUAGE";
116 case XMediaTagAttribute::kAutoselect:
117 return "AUTOSELECT";
118 case XMediaTagAttribute::kChannels:
119 return "CHANNELS";
120 case XMediaTagAttribute::kCharacteristics:
121 return "CHARACTERISTICS";
122 case XMediaTagAttribute::kDefault:
123 return "DEFAULT";
124 case XMediaTagAttribute::kForced:
125 return "FORCED";
126 case XMediaTagAttribute::kGroupId:
127 return "GROUP-ID";
128 case XMediaTagAttribute::kInstreamId:
129 return "INSTREAM-ID";
130 case XMediaTagAttribute::kLanguage:
131 return "LANGUAGE";
132 case XMediaTagAttribute::kName:
133 return "NAME";
134 case XMediaTagAttribute::kStableRenditionId:
135 return "STABLE-RENDITION-ID";
136 case XMediaTagAttribute::kType:
137 return "TYPE";
138 case XMediaTagAttribute::kUri:
139 return "URI";
140 }
141
Peter Boström0ce6af602023-05-15 21:27:24142 NOTREACHED_NORETURN();
Will Cassella2f4c3202022-08-11 00:20:19143}
144
Will Cassella58c7fc632022-04-06 18:48:39145// Attributes expected in `EXT-X-STREAM-INF` tag contents.
146// These must remain sorted alphabetically.
147enum class XStreamInfTagAttribute {
Will Cassellaad9c3402022-09-08 22:52:53148 kAudio,
Will Cassella58c7fc632022-04-06 18:48:39149 kAverageBandwidth,
150 kBandwidth,
151 kCodecs,
Will Cassellacf1c3912022-04-28 20:55:58152 kFrameRate,
Will Cassella58c7fc632022-04-06 18:48:39153 kProgramId, // Ignored for backwards compatibility
Will Cassellacf1c3912022-04-28 20:55:58154 kResolution,
Will Cassella58c7fc632022-04-06 18:48:39155 kScore,
Ted Meyer274348b2023-02-03 00:34:31156 kVideo,
157 kMaxValue = kVideo,
Will Cassella58c7fc632022-04-06 18:48:39158};
159
Ted Meyerda164052024-03-04 05:46:24160constexpr std::string_view GetAttributeName(XStreamInfTagAttribute attribute) {
Will Cassella58c7fc632022-04-06 18:48:39161 switch (attribute) {
Will Cassellaad9c3402022-09-08 22:52:53162 case XStreamInfTagAttribute::kAudio:
163 return "AUDIO";
Ted Meyer274348b2023-02-03 00:34:31164 case XStreamInfTagAttribute::kVideo:
165 return "VIDEO";
Will Cassella58c7fc632022-04-06 18:48:39166 case XStreamInfTagAttribute::kAverageBandwidth:
167 return "AVERAGE-BANDWIDTH";
168 case XStreamInfTagAttribute::kBandwidth:
169 return "BANDWIDTH";
170 case XStreamInfTagAttribute::kCodecs:
171 return "CODECS";
Will Cassellacf1c3912022-04-28 20:55:58172 case XStreamInfTagAttribute::kFrameRate:
173 return "FRAME-RATE";
Will Cassella58c7fc632022-04-06 18:48:39174 case XStreamInfTagAttribute::kProgramId:
175 return "PROGRAM-ID";
Will Cassellacf1c3912022-04-28 20:55:58176 case XStreamInfTagAttribute::kResolution:
177 return "RESOLUTION";
Will Cassella58c7fc632022-04-06 18:48:39178 case XStreamInfTagAttribute::kScore:
179 return "SCORE";
180 }
181
Peter Boström0ce6af602023-05-15 21:27:24182 NOTREACHED_NORETURN();
Will Cassella58c7fc632022-04-06 18:48:39183}
184
Ted Meyerf7566d32023-12-06 02:31:12185// Attributes expected in `EXT-X-SKIP` tag contents.
186// These must remain sorted alphabetically.
187enum class XSkipTagAttribute {
188 kRecentlyRemovedDateranges,
189 kSkippedSegments,
190 kMaxValue = kSkippedSegments,
191};
192
193constexpr std::string_view GetAttributeName(XSkipTagAttribute attribute) {
194 switch (attribute) {
195 case XSkipTagAttribute::kRecentlyRemovedDateranges:
196 return "RECENTLY-REMOVED-DATERANGES";
197 case XSkipTagAttribute::kSkippedSegments:
198 return "SKIPPED-SEGMENTS";
199 }
200 NOTREACHED_NORETURN();
201}
202
Ted Meyere55bc2b2024-02-24 00:31:25203enum class XRenditionReportTagAttribute {
204 kLastMSN,
205 kLastPart,
206 kUri,
207 kMaxValue = kUri,
208};
209
210constexpr std::string_view GetAttributeName(XRenditionReportTagAttribute attr) {
211 switch (attr) {
212 case XRenditionReportTagAttribute::kUri:
213 return "URI";
214 case XRenditionReportTagAttribute::kLastMSN:
215 return "LAST-MSN";
216 case XRenditionReportTagAttribute::kLastPart:
217 return "LAST-PART";
218 }
219 NOTREACHED_NORETURN();
220}
221
Will Cassellafc185a692022-07-21 00:29:51222// Attributes expected in `EXT-X-MAP` tag contents.
223// These must remain sorted alphabetically.
224enum class XMapTagAttribute {
225 kByteRange,
226 kUri,
227 kMaxValue = kUri,
228};
229
Ted Meyerda164052024-03-04 05:46:24230constexpr std::string_view GetAttributeName(XMapTagAttribute attribute) {
Will Cassellafc185a692022-07-21 00:29:51231 switch (attribute) {
232 case XMapTagAttribute::kByteRange:
233 return "BYTERANGE";
234 case XMapTagAttribute::kUri:
235 return "URI";
236 }
237
Peter Boström0ce6af602023-05-15 21:27:24238 NOTREACHED_NORETURN();
Will Cassellafc185a692022-07-21 00:29:51239}
240
Will Cassella53a6da12022-07-14 00:53:36241// Attributes expected in `EXT-X-PART` tag contents.
242// These must remain sorted alphabetically.
243enum class XPartTagAttribute {
244 kByteRange,
245 kDuration,
246 kGap,
247 kIndependent,
248 kUri,
249 kMaxValue = kUri,
250};
251
Ted Meyerda164052024-03-04 05:46:24252constexpr std::string_view GetAttributeName(XPartTagAttribute attribute) {
Will Cassella53a6da12022-07-14 00:53:36253 switch (attribute) {
254 case XPartTagAttribute::kByteRange:
255 return "BYTERANGE";
256 case XPartTagAttribute::kDuration:
257 return "DURATION";
258 case XPartTagAttribute::kGap:
259 return "GAP";
260 case XPartTagAttribute::kIndependent:
261 return "INDEPENDENT";
262 case XPartTagAttribute::kUri:
263 return "URI";
264 }
265}
266
Will Cassella0412e58a2022-07-20 21:35:38267// Attributes expected in `EXT-X-PART-INF` tag contents.
268// These must remain sorted alphabetically.
269enum class XPartInfTagAttribute {
270 kPartTarget,
271 kMaxValue = kPartTarget,
272};
273
Ted Meyerda164052024-03-04 05:46:24274constexpr std::string_view GetAttributeName(XPartInfTagAttribute attribute) {
Will Cassella0412e58a2022-07-20 21:35:38275 switch (attribute) {
276 case XPartInfTagAttribute::kPartTarget:
277 return "PART-TARGET";
278 }
279
Peter Boström0ce6af602023-05-15 21:27:24280 NOTREACHED_NORETURN();
Will Cassella0412e58a2022-07-20 21:35:38281}
282
Will Cassellafc185a692022-07-21 00:29:51283// Attributes expected in `EXT-X-SERVER-CONTROL` tag contents.
Will Cassellacfa1b1b42022-06-17 20:50:52284// These must remain sorted alphabetically.
285enum class XServerControlTagAttribute {
286 kCanBlockReload,
287 kCanSkipDateRanges,
288 kCanSkipUntil,
289 kHoldBack,
290 kPartHoldBack,
291 kMaxValue = kPartHoldBack,
292};
293
Ted Meyerda164052024-03-04 05:46:24294constexpr std::string_view GetAttributeName(
Will Cassellacfa1b1b42022-06-17 20:50:52295 XServerControlTagAttribute attribute) {
296 switch (attribute) {
297 case XServerControlTagAttribute::kCanBlockReload:
298 return "CAN-BLOCK-RELOAD";
299 case XServerControlTagAttribute::kCanSkipDateRanges:
300 return "CAN-SKIP-DATERANGES";
301 case XServerControlTagAttribute::kCanSkipUntil:
302 return "CAN-SKIP-UNTIL";
303 case XServerControlTagAttribute::kHoldBack:
304 return "HOLD-BACK";
305 case XServerControlTagAttribute::kPartHoldBack:
306 return "PART-HOLD-BACK";
307 }
308
Peter Boström0ce6af602023-05-15 21:27:24309 NOTREACHED_NORETURN();
Will Cassellacfa1b1b42022-06-17 20:50:52310}
311
Ted Meyer54a32922024-06-14 21:51:16312enum class XKeyTagAttribute {
313 kIv,
314 kKeyFormat,
315 kKeyFormatVersions,
316 kMethod,
317 kUri,
318 kMaxValue = kUri,
319};
320
321constexpr std::string_view GetAttributeName(XKeyTagAttribute attribute) {
322 switch (attribute) {
323 case XKeyTagAttribute::kIv:
324 return "IV";
325 case XKeyTagAttribute::kKeyFormat:
326 return "KEYFORMAT";
327 case XKeyTagAttribute::kKeyFormatVersions:
328 return "KEYFORMATVERSIONS";
329 case XKeyTagAttribute::kMethod:
330 return "METHOD";
331 case XKeyTagAttribute::kUri:
332 return "URI";
333 }
334 NOTREACHED_NORETURN();
335}
336
Will Cassellaf28a25262022-01-26 20:24:31337template <typename T, size_t kLast>
338constexpr bool IsAttributeEnumSorted(std::index_sequence<kLast>) {
339 return true;
340}
341
342template <typename T, size_t kLHS, size_t kRHS, size_t... kRest>
343constexpr bool IsAttributeEnumSorted(
344 std::index_sequence<kLHS, kRHS, kRest...>) {
345 const auto lhs = GetAttributeName(static_cast<T>(kLHS));
346 const auto rhs = GetAttributeName(static_cast<T>(kRHS));
347 return lhs < rhs &&
348 IsAttributeEnumSorted<T>(std::index_sequence<kRHS, kRest...>{});
349}
350
351// Wraps `AttributeMap::MakeStorage` by mapping the (compile-time) sequence
352// of size_t's to a sequence of the corresponding attribute enum names.
353template <typename T, std::size_t... Indices>
354constexpr std::array<types::AttributeMap::Item, sizeof...(Indices)>
355MakeTypedAttributeMapStorage(std::index_sequence<Indices...> seq) {
356 static_assert(IsAttributeEnumSorted<T>(seq),
357 "Enum keys must be sorted alphabetically");
358 return types::AttributeMap::MakeStorage(
359 GetAttributeName(static_cast<T>(Indices))...);
360}
361
362// Helper for using AttributeMap with an enum of keys.
363// The result of running `GetAttributeName` across `0..T::kMaxValue` (inclusive)
364// must produced an ordered set of strings.
365template <typename T>
366struct TypedAttributeMap {
367 static_assert(std::is_enum<T>::value, "T must be an enum");
368 static_assert(std::is_same<decltype(GetAttributeName(std::declval<T>())),
Ted Meyerda164052024-03-04 05:46:24369 std::string_view>::value,
Will Cassellaf28a25262022-01-26 20:24:31370 "GetAttributeName must be overloaded for T to return a "
Ted Meyerda164052024-03-04 05:46:24371 "std::string_view");
Will Cassellaf28a25262022-01-26 20:24:31372 static constexpr size_t kNumKeys = static_cast<size_t>(T::kMaxValue) + 1;
373
374 TypedAttributeMap()
375 : attributes_(MakeTypedAttributeMapStorage<T>(
376 std::make_index_sequence<kNumKeys>())) {}
377
378 // Wraps `AttributeMap::FillUntilError` using the built-in storage object.
379 ParseStatus FillUntilError(types::AttributeListIterator* iter) {
380 types::AttributeMap map(attributes_);
381 return map.FillUntilError(iter);
382 }
383
384 // Returns whether the entry corresponding to the given key has a value.
385 bool HasValue(T key) const {
386 return attributes_[static_cast<size_t>(key)].second.has_value();
387 }
388
389 // Returns the value stored in the entry for the given key.
390 SourceString GetValue(T key) const {
391 return attributes_[static_cast<size_t>(key)].second.value();
392 }
393
Ted Meyer54a32922024-06-14 21:51:16394 size_t Size() const {
395 return std::count_if(attributes_.begin(), attributes_.end(),
396 [](const types::AttributeMap::Item& item) {
397 return item.second.has_value();
398 });
399 }
400
Will Cassellaf28a25262022-01-26 20:24:31401 private:
402 std::array<types::AttributeMap::Item, kNumKeys> attributes_;
403};
404
Ted Meyer38a01192024-06-06 22:53:44405#define RETURN_IF_ERROR(var_not_expr) \
406 do { \
407 if (!var_not_expr.has_value()) { \
408 return std::move(var_not_expr).error().AddHere(); \
409 } \
410 } while (0)
411
412template <template <typename...> typename SpecifiedContainer, typename Type>
413struct is_specialization_of : std::false_type {};
414
415template <template <typename...> typename SpecifiedContainer, typename... Types>
416struct is_specialization_of<SpecifiedContainer, SpecifiedContainer<Types...>>
417 : std::true_type {};
418
419// ParseField exists to help with the pattern where we need to parse some field
420// from a dictionary of tag attributes where some of those fields are optional.
421// It is designed to provide support for either optional or required fields,
422// depending on the return-type specialization:
423// When Result=std::optional<X>, ParseField will return a nullopt rather than
424// an error if the `field_name` field can't be found in `map`.
425// When Result=T (where T is not std::optional<X>), ParseField will fail with
426// a ParseStatus if `field_name` is not found in map.
427// A function pointer must also be provided which does the parsing from
428// SourceString => Result, provided that the `field_name` key is present.
429template <typename Result,
430 typename AttrEnum,
431 typename ParseFn,
432 typename... ParseFnArgs>
433ParseStatus::Or<Result> ParseField(AttrEnum field_name,
434 TypedAttributeMap<AttrEnum> map,
435 ParseFn parser,
436 ParseFnArgs&&... args) {
437 if (map.HasValue(field_name)) {
438 auto maybe =
439 parser(map.GetValue(field_name), std::forward<ParseFnArgs>(args)...);
440 if (!maybe.has_value()) {
441 ParseStatus result = ParseStatusCode::kMalformedTag;
442 return std::move(result).AddCause(std::move(maybe).error());
443 }
444 return Result(std::move(maybe).value());
445 }
446 if constexpr (is_specialization_of<std::optional, Result>::value) {
447 return Result(std::nullopt);
448 }
449 return ParseStatusCode::kMalformedTag;
450}
451
Will Cassella058de252022-01-22 01:38:20452} // namespace
453
Will Cassella5343f8f2022-07-20 00:11:33454// static
Will Cassella058de252022-01-22 01:38:20455ParseStatus::Or<M3uTag> M3uTag::Parse(TagItem tag) {
456 return ParseEmptyTag<M3uTag>(tag);
Will Cassella42480ee2022-01-07 21:56:11457}
458
Will Cassella5343f8f2022-07-20 00:11:33459// static
Will Cassellaf28a25262022-01-26 20:24:31460XDefineTag XDefineTag::CreateDefinition(types::VariableName name,
Ted Meyerda164052024-03-04 05:46:24461 std::string_view value) {
Will Cassellaf28a25262022-01-26 20:24:31462 return XDefineTag{.name = name, .value = value};
463}
464
Will Cassella5343f8f2022-07-20 00:11:33465// static
Will Cassellaf28a25262022-01-26 20:24:31466XDefineTag XDefineTag::CreateImport(types::VariableName name) {
Arthur Sonzogni581b9e82024-01-30 17:25:15467 return XDefineTag{.name = name, .value = std::nullopt};
Will Cassellaf28a25262022-01-26 20:24:31468}
469
Will Cassella5343f8f2022-07-20 00:11:33470// static
Will Cassellaf28a25262022-01-26 20:24:31471ParseStatus::Or<XDefineTag> XDefineTag::Parse(TagItem tag) {
Will Cassellaee56fa572022-04-12 19:17:28472 DCHECK(tag.GetName() == ToTagName(XDefineTag::kName));
473
474 if (!tag.GetContent().has_value()) {
475 return ParseStatusCode::kMalformedTag;
476 }
Will Cassella85694d62022-04-02 01:08:01477
478 // Parse the attribute-list.
479 // Quoted strings in EXT-X-DEFINE tags are unique in that they aren't subject
480 // to variable substitution. For that reason, we use the
481 // `ParseQuotedStringWithoutSubstitution` function here.
Will Cassellaf28a25262022-01-26 20:24:31482 TypedAttributeMap<XDefineTagAttribute> map;
Will Cassellaee56fa572022-04-12 19:17:28483 types::AttributeListIterator iter(*tag.GetContent());
Will Cassellaf28a25262022-01-26 20:24:31484 auto result = map.FillUntilError(&iter);
485
486 if (result.code() != ParseStatusCode::kReachedEOF) {
487 return ParseStatus(ParseStatusCode::kMalformedTag)
488 .AddCause(std::move(result));
489 }
490
491 // "NAME" and "IMPORT" are mutually exclusive
492 if (map.HasValue(XDefineTagAttribute::kName) &&
493 map.HasValue(XDefineTagAttribute::kImport)) {
494 return ParseStatusCode::kMalformedTag;
495 }
496
497 if (map.HasValue(XDefineTagAttribute::kName)) {
Will Cassella85694d62022-04-02 01:08:01498 auto var_name = types::ParseQuotedStringWithoutSubstitution(
499 map.GetValue(XDefineTagAttribute::kName))
500 .MapValue(types::VariableName::Parse);
Ted Meyer0eb6fcc2022-10-21 03:21:25501 if (!var_name.has_value()) {
Will Cassellaf28a25262022-01-26 20:24:31502 return ParseStatus(ParseStatusCode::kMalformedTag)
503 .AddCause(std::move(var_name).error());
504 }
505
506 // If "NAME" is defined, "VALUE" must also be defined
507 if (!map.HasValue(XDefineTagAttribute::kValue)) {
508 return ParseStatusCode::kMalformedTag;
509 }
510
Will Cassella85694d62022-04-02 01:08:01511 auto value = types::ParseQuotedStringWithoutSubstitution(
Will Cassella335a0142022-07-01 18:20:52512 map.GetValue(XDefineTagAttribute::kValue), /*allow_empty*/ true);
Ted Meyer0eb6fcc2022-10-21 03:21:25513 if (!value.has_value()) {
Will Cassellaf28a25262022-01-26 20:24:31514 return ParseStatus(ParseStatusCode::kMalformedTag);
515 }
516
517 return XDefineTag::CreateDefinition(std::move(var_name).value(),
518 std::move(value).value().Str());
519 }
520
521 if (map.HasValue(XDefineTagAttribute::kImport)) {
Will Cassella85694d62022-04-02 01:08:01522 auto var_name = types::ParseQuotedStringWithoutSubstitution(
523 map.GetValue(XDefineTagAttribute::kImport))
524 .MapValue(types::VariableName::Parse);
Ted Meyer0eb6fcc2022-10-21 03:21:25525 if (!var_name.has_value()) {
Will Cassellaf28a25262022-01-26 20:24:31526 return ParseStatus(ParseStatusCode::kMalformedTag)
527 .AddCause(std::move(var_name).error());
528 }
529
530 // "VALUE" doesn't make any sense here, but the spec doesn't explicitly
531 // forbid it. It may be used in the future to provide a default value for
532 // undefined imported variables, so we won't error on it.
533 return XDefineTag::CreateImport(std::move(var_name).value());
534 }
535
536 // Without "NAME" or "IMPORT", the tag is malformed
537 return ParseStatusCode::kMalformedTag;
538}
539
Will Cassella5343f8f2022-07-20 00:11:33540// static
Will Cassella0412e58a2022-07-20 21:35:38541ParseStatus::Or<XIndependentSegmentsTag> XIndependentSegmentsTag::Parse(
542 TagItem tag) {
543 return ParseEmptyTag<XIndependentSegmentsTag>(tag);
544}
Will Cassella04297882022-04-06 01:39:37545
Will Cassella0412e58a2022-07-20 21:35:38546// static
547ParseStatus::Or<XVersionTag> XVersionTag::Parse(TagItem tag) {
548 auto result = ParseDecimalIntegerTag(tag, &XVersionTag::version);
Ted Meyer0eb6fcc2022-10-21 03:21:25549 if (!result.has_value()) {
Will Cassella0412e58a2022-07-20 21:35:38550 return std::move(result).error();
Will Cassella04297882022-04-06 01:39:37551 }
552
Will Cassella0412e58a2022-07-20 21:35:38553 // Reject invalid version numbers.
554 // For valid version numbers, caller will decide if the version is supported.
555 auto out = std::move(result).value();
556 if (out.version == 0) {
557 return ParseStatusCode::kInvalidPlaylistVersion;
Will Cassella04297882022-04-06 01:39:37558 }
559
Will Cassella0412e58a2022-07-20 21:35:38560 return out;
Will Cassella04297882022-04-06 01:39:37561}
562
Will Cassella2f4c3202022-08-11 00:20:19563struct XMediaTag::CtorArgs {
564 decltype(XMediaTag::type) type;
565 decltype(XMediaTag::uri) uri;
566 decltype(XMediaTag::instream_id) instream_id;
567 decltype(XMediaTag::group_id) group_id;
568 decltype(XMediaTag::language) language;
569 decltype(XMediaTag::associated_language) associated_language;
570 decltype(XMediaTag::name) name;
571 decltype(XMediaTag::stable_rendition_id) stable_rendition_id;
572 decltype(XMediaTag::is_default) is_default;
573 decltype(XMediaTag::autoselect) autoselect;
574 decltype(XMediaTag::forced) forced;
575 decltype(XMediaTag::characteristics) characteristics;
576 decltype(XMediaTag::channels) channels;
577};
578
579XMediaTag::XMediaTag(CtorArgs args)
580 : type(std::move(args.type)),
581 uri(std::move(args.uri)),
582 instream_id(std::move(args.instream_id)),
583 group_id(std::move(args.group_id)),
584 language(std::move(args.language)),
585 associated_language(std::move(args.associated_language)),
586 name(std::move(args.name)),
587 stable_rendition_id(std::move(args.stable_rendition_id)),
588 is_default(std::move(args.is_default)),
589 autoselect(std::move(args.autoselect)),
590 forced(std::move(args.forced)),
591 characteristics(std::move(args.characteristics)),
592 channels(std::move(args.channels)) {}
593
594XMediaTag::~XMediaTag() = default;
595
596XMediaTag::XMediaTag(const XMediaTag&) = default;
597
598XMediaTag::XMediaTag(XMediaTag&&) = default;
599
600XMediaTag& XMediaTag::operator=(const XMediaTag&) = default;
601
602XMediaTag& XMediaTag::operator=(XMediaTag&&) = default;
603
604// static
605ParseStatus::Or<XMediaTag> XMediaTag::Parse(
606 TagItem tag,
607 const VariableDictionary& variable_dict,
608 VariableDictionary::SubstitutionBuffer& sub_buffer) {
609 DCHECK(tag.GetName() == ToTagName(XMediaTag::kName));
610 if (!tag.GetContent().has_value()) {
611 return ParseStatusCode::kMalformedTag;
612 }
613
614 // Parse the attribute-list
615 TypedAttributeMap<XMediaTagAttribute> map;
616 types::AttributeListIterator iter(*tag.GetContent());
617 auto map_result = map.FillUntilError(&iter);
618
619 if (map_result.code() != ParseStatusCode::kReachedEOF) {
620 return ParseStatus(ParseStatusCode::kMalformedTag)
621 .AddCause(std::move(map_result));
622 }
623
624 // Parse the 'TYPE' attribute
625 MediaType type;
626 if (map.HasValue(XMediaTagAttribute::kType)) {
627 auto str = map.GetValue(XMediaTagAttribute::kType);
628 if (str.Str() == "AUDIO") {
629 type = MediaType::kAudio;
630 } else if (str.Str() == "VIDEO") {
631 type = MediaType::kVideo;
632 } else if (str.Str() == "SUBTITLES") {
633 type = MediaType::kSubtitles;
634 } else if (str.Str() == "CLOSED-CAPTIONS") {
635 type = MediaType::kClosedCaptions;
636 } else {
637 return ParseStatusCode::kMalformedTag;
638 }
639 } else {
640 return ParseStatusCode::kMalformedTag;
641 }
642
643 // Parse the 'URI' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15644 std::optional<ResolvedSourceString> uri;
Will Cassella2f4c3202022-08-11 00:20:19645 if (map.HasValue(XMediaTagAttribute::kUri)) {
646 // This attribute MUST NOT be defined for closed-captions renditions
647 if (type == MediaType::kClosedCaptions) {
648 return ParseStatusCode::kMalformedTag;
649 }
650
651 auto result = types::ParseQuotedString(
652 map.GetValue(XMediaTagAttribute::kUri), variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25653 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19654 return ParseStatus(ParseStatusCode::kMalformedTag)
655 .AddCause(std::move(result).error());
656 }
657
658 uri = std::move(result).value();
659 } else {
660 // URI MUST be defined for subtitle renditions
661 if (type == MediaType::kSubtitles) {
662 return ParseStatusCode::kMalformedTag;
663 }
664 }
665
666 // Parse the 'GROUP-ID' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15667 std::optional<ResolvedSourceString> group_id;
Will Cassella2f4c3202022-08-11 00:20:19668 if (map.HasValue(XMediaTagAttribute::kGroupId)) {
669 auto result = types::ParseQuotedString(
670 map.GetValue(XMediaTagAttribute::kGroupId), variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25671 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19672 return ParseStatus(ParseStatusCode::kMalformedTag)
673 .AddCause(std::move(result).error());
674 }
675
676 group_id = std::move(result).value();
677 } else {
678 return ParseStatusCode::kMalformedTag;
679 }
680
681 // Parse the 'LANGUAGE' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15682 std::optional<ResolvedSourceString> language;
Will Cassella2f4c3202022-08-11 00:20:19683 if (map.HasValue(XMediaTagAttribute::kLanguage)) {
684 auto result = types::ParseQuotedString(
685 map.GetValue(XMediaTagAttribute::kLanguage), variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25686 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19687 return ParseStatus(ParseStatusCode::kMalformedTag)
688 .AddCause(std::move(result).error());
689 }
690
691 language = std::move(result).value();
692 }
693
694 // Parse the 'ASSOC-LANGUAGE' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15695 std::optional<ResolvedSourceString> assoc_language;
Will Cassella2f4c3202022-08-11 00:20:19696 if (map.HasValue(XMediaTagAttribute::kAssocLanguage)) {
697 auto result = types::ParseQuotedString(
698 map.GetValue(XMediaTagAttribute::kAssocLanguage), variable_dict,
699 sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25700 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19701 return ParseStatus(ParseStatusCode::kMalformedTag)
702 .AddCause(std::move(result).error());
703 }
704
705 assoc_language = std::move(result).value();
706 }
707
708 // Parse the 'NAME' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15709 std::optional<ResolvedSourceString> name;
Will Cassella2f4c3202022-08-11 00:20:19710 if (map.HasValue(XMediaTagAttribute::kName)) {
711 auto result = types::ParseQuotedString(
712 map.GetValue(XMediaTagAttribute::kName), variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25713 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19714 return ParseStatus(ParseStatusCode::kMalformedTag)
715 .AddCause(std::move(result).error());
716 }
717
718 name = std::move(result).value();
719 } else {
720 return ParseStatusCode::kMalformedTag;
721 }
722
723 // Parse the 'STABLE-RENDITION-ID' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15724 std::optional<types::StableId> stable_rendition_id;
Will Cassella2f4c3202022-08-11 00:20:19725 if (map.HasValue(XMediaTagAttribute::kStableRenditionId)) {
726 auto result = types::ParseQuotedString(
727 map.GetValue(XMediaTagAttribute::kStableRenditionId),
728 variable_dict, sub_buffer)
729 .MapValue(types::StableId::Parse);
Ted Meyer0eb6fcc2022-10-21 03:21:25730 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19731 return ParseStatus(ParseStatusCode::kMalformedTag)
732 .AddCause(std::move(result).error());
733 }
734 stable_rendition_id = std::move(result).value();
735 }
736
737 // Parse the 'DEFAULT' attribute
738 bool is_default = false;
739 if (map.HasValue(XMediaTagAttribute::kDefault)) {
740 if (map.GetValue(XMediaTagAttribute::kDefault).Str() == "YES") {
741 is_default = true;
742 }
743 }
744
745 // Parse the 'AUTOSELECT' attribute
Will Cassellaf1cd38372022-09-08 20:19:55746 bool autoselect = is_default;
Will Cassella2f4c3202022-08-11 00:20:19747 if (map.HasValue(XMediaTagAttribute::kAutoselect)) {
748 if (map.GetValue(XMediaTagAttribute::kAutoselect).Str() == "YES") {
749 autoselect = true;
750 } else if (is_default) {
751 // If the 'DEFAULT' attribute is 'YES', then the value of this attribute
752 // must also be 'YES', if present.
753 return ParseStatusCode::kMalformedTag;
754 }
755 }
756
757 // Parse the 'FORCED' attribute
758 bool forced = false;
759 if (map.HasValue(XMediaTagAttribute::kForced)) {
760 // The FORCED attribute MUST NOT be present unless TYPE=SUBTITLES
761 if (type != MediaType::kSubtitles) {
762 return ParseStatusCode::kMalformedTag;
763 }
764
765 if (map.GetValue(XMediaTagAttribute::kForced).Str() == "YES") {
766 forced = true;
767 }
768 }
769
770 // Parse the 'INSTREAM-ID' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15771 std::optional<types::InstreamId> instream_id;
Will Cassella2f4c3202022-08-11 00:20:19772 if (map.HasValue(XMediaTagAttribute::kInstreamId)) {
773 // The INSTREAM-ID attribute MUST NOT be present unless TYPE=CLOSED-CAPTIONS
774 if (type != MediaType::kClosedCaptions) {
775 return ParseStatusCode::kMalformedTag;
776 }
777
778 auto result =
779 types::ParseQuotedString(map.GetValue(XMediaTagAttribute::kInstreamId),
780 variable_dict, sub_buffer)
781 .MapValue(types::InstreamId::Parse);
Ted Meyer0eb6fcc2022-10-21 03:21:25782 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19783 return ParseStatus(ParseStatusCode::kMalformedTag)
784 .AddCause(std::move(result).error());
785 }
786
787 instream_id = std::move(result).value();
788 }
789 // This attribute is REQUIRED if TYPE=CLOSED-CAPTIONS
790 else if (type == MediaType::kClosedCaptions) {
791 return ParseStatusCode::kMalformedTag;
792 }
793
794 // Parse the 'CHARACTERISTICS' attribute
795 std::vector<std::string> characteristics;
796 if (map.HasValue(XMediaTagAttribute::kCharacteristics)) {
797 auto result = types::ParseQuotedString(
798 map.GetValue(XMediaTagAttribute::kCharacteristics), variable_dict,
799 sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25800 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19801 return ParseStatus(ParseStatusCode::kMalformedTag)
802 .AddCause(std::move(result).error());
803 }
804 auto value = std::move(result).value();
805
806 while (!value.Empty()) {
807 const auto mct = value.ConsumeDelimiter(',');
808 if (mct.Empty()) {
809 return ParseStatusCode::kMalformedTag;
810 }
811
812 characteristics.emplace_back(mct.Str());
813 }
814 }
815
816 // Parse the 'CHANNELS' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15817 std::optional<types::AudioChannels> channels;
Will Cassella2f4c3202022-08-11 00:20:19818 if (map.HasValue(XMediaTagAttribute::kChannels)) {
819 // Currently only supported type with channel information is `kAudio`.
820 if (type == MediaType::kAudio) {
821 auto result =
822 types::ParseQuotedString(map.GetValue(XMediaTagAttribute::kChannels),
823 variable_dict, sub_buffer)
824 .MapValue(types::AudioChannels::Parse);
Ted Meyer0eb6fcc2022-10-21 03:21:25825 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19826 return ParseStatus(ParseStatusCode::kMalformedTag)
827 .AddCause(std::move(result).error());
828 }
829
830 channels = std::move(result).value();
831 }
832 }
833
834 return XMediaTag(XMediaTag::CtorArgs{
835 .type = type,
836 .uri = uri,
837 .instream_id = instream_id,
838 .group_id = group_id.value(),
839 .language = language,
840 .associated_language = assoc_language,
841 .name = name.value(),
842 .stable_rendition_id = std::move(stable_rendition_id),
843 .is_default = is_default,
844 .autoselect = autoselect,
845 .forced = forced,
846 .characteristics = std::move(characteristics),
847 .channels = std::move(channels),
848 });
849}
850
Will Cassella58c7fc632022-04-06 18:48:39851XStreamInfTag::XStreamInfTag() = default;
852
853XStreamInfTag::~XStreamInfTag() = default;
854
855XStreamInfTag::XStreamInfTag(const XStreamInfTag&) = default;
856
857XStreamInfTag::XStreamInfTag(XStreamInfTag&&) = default;
858
859XStreamInfTag& XStreamInfTag::operator=(const XStreamInfTag&) = default;
860
861XStreamInfTag& XStreamInfTag::operator=(XStreamInfTag&&) = default;
862
Will Cassella5343f8f2022-07-20 00:11:33863// static
Will Cassella58c7fc632022-04-06 18:48:39864ParseStatus::Or<XStreamInfTag> XStreamInfTag::Parse(
865 TagItem tag,
866 const VariableDictionary& variable_dict,
867 VariableDictionary::SubstitutionBuffer& sub_buffer) {
Will Cassellaee56fa572022-04-12 19:17:28868 DCHECK(tag.GetName() == ToTagName(XStreamInfTag::kName));
Will Cassella58c7fc632022-04-06 18:48:39869 XStreamInfTag out;
870
Will Cassellaee56fa572022-04-12 19:17:28871 if (!tag.GetContent().has_value()) {
872 return ParseStatusCode::kMalformedTag;
873 }
874
Will Cassella58c7fc632022-04-06 18:48:39875 // Parse the attribute-list
876 TypedAttributeMap<XStreamInfTagAttribute> map;
Will Cassellaee56fa572022-04-12 19:17:28877 types::AttributeListIterator iter(*tag.GetContent());
Will Cassella58c7fc632022-04-06 18:48:39878 auto map_result = map.FillUntilError(&iter);
879
880 if (map_result.code() != ParseStatusCode::kReachedEOF) {
881 return ParseStatus(ParseStatusCode::kMalformedTag)
882 .AddCause(std::move(map_result));
883 }
884
885 // Extract the 'BANDWIDTH' attribute
886 if (map.HasValue(XStreamInfTagAttribute::kBandwidth)) {
887 auto bandwidth = types::ParseDecimalInteger(
Will Cassella0d3ed6a2022-06-27 19:39:29888 map.GetValue(XStreamInfTagAttribute::kBandwidth)
889 .SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:25890 if (!bandwidth.has_value()) {
Will Cassella58c7fc632022-04-06 18:48:39891 return ParseStatus(ParseStatusCode::kMalformedTag)
892 .AddCause(std::move(bandwidth).error());
893 }
894
895 out.bandwidth = std::move(bandwidth).value();
896 } else {
897 return ParseStatusCode::kMalformedTag;
898 }
899
900 // Extract the 'AVERAGE-BANDWIDTH' attribute
901 if (map.HasValue(XStreamInfTagAttribute::kAverageBandwidth)) {
902 auto average_bandwidth = types::ParseDecimalInteger(
Will Cassella0d3ed6a2022-06-27 19:39:29903 map.GetValue(XStreamInfTagAttribute::kAverageBandwidth)
904 .SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:25905 if (!average_bandwidth.has_value()) {
Will Cassella58c7fc632022-04-06 18:48:39906 return ParseStatus(ParseStatusCode::kMalformedTag)
907 .AddCause(std::move(average_bandwidth).error());
908 }
909
910 out.average_bandwidth = std::move(average_bandwidth).value();
911 }
912
913 // Extract the 'SCORE' attribute
914 if (map.HasValue(XStreamInfTagAttribute::kScore)) {
915 auto score = types::ParseDecimalFloatingPoint(
Will Cassella0d3ed6a2022-06-27 19:39:29916 map.GetValue(XStreamInfTagAttribute::kScore)
917 .SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:25918 if (!score.has_value()) {
Will Cassella58c7fc632022-04-06 18:48:39919 return ParseStatus(ParseStatusCode::kMalformedTag)
920 .AddCause(std::move(score).error());
921 }
922
923 out.score = std::move(score).value();
924 }
925
926 // Extract the 'CODECS' attribute
927 if (map.HasValue(XStreamInfTagAttribute::kCodecs)) {
Will Cassella9af5f782022-09-08 21:10:36928 auto codecs_string =
Will Cassella58c7fc632022-04-06 18:48:39929 types::ParseQuotedString(map.GetValue(XStreamInfTagAttribute::kCodecs),
930 variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25931 if (!codecs_string.has_value()) {
Will Cassella58c7fc632022-04-06 18:48:39932 return ParseStatus(ParseStatusCode::kMalformedTag)
Will Cassella9af5f782022-09-08 21:10:36933 .AddCause(std::move(codecs_string).error());
Will Cassella58c7fc632022-04-06 18:48:39934 }
Will Cassella9af5f782022-09-08 21:10:36935
936 // Split the list of codecs
937 std::vector<std::string> codecs;
938 SplitCodecs(std::move(codecs_string).value().Str(), &codecs);
939 out.codecs = std::move(codecs);
Will Cassella58c7fc632022-04-06 18:48:39940 }
941
Will Cassellacf1c3912022-04-28 20:55:58942 // Extract the 'RESOLUTION' attribute
943 if (map.HasValue(XStreamInfTagAttribute::kResolution)) {
944 auto resolution = types::DecimalResolution::Parse(
Will Cassella0d3ed6a2022-06-27 19:39:29945 map.GetValue(XStreamInfTagAttribute::kResolution)
946 .SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:25947 if (!resolution.has_value()) {
Will Cassellacf1c3912022-04-28 20:55:58948 return ParseStatus(ParseStatusCode::kMalformedTag)
949 .AddCause(std::move(resolution).error());
950 }
951 out.resolution = std::move(resolution).value();
952 }
953
954 // Extract the 'FRAME-RATE' attribute
955 if (map.HasValue(XStreamInfTagAttribute::kFrameRate)) {
956 auto frame_rate = types::ParseDecimalFloatingPoint(
Will Cassella0d3ed6a2022-06-27 19:39:29957 map.GetValue(XStreamInfTagAttribute::kFrameRate)
958 .SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:25959 if (!frame_rate.has_value()) {
Will Cassellacf1c3912022-04-28 20:55:58960 return ParseStatus(ParseStatusCode::kMalformedTag)
961 .AddCause(std::move(frame_rate).error());
962 }
963 out.frame_rate = std::move(frame_rate).value();
964 }
965
Will Cassellaad9c3402022-09-08 22:52:53966 // Extract the 'AUDIO' attribute
967 if (map.HasValue(XStreamInfTagAttribute::kAudio)) {
968 auto audio =
969 types::ParseQuotedString(map.GetValue(XStreamInfTagAttribute::kAudio),
970 variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25971 if (!audio.has_value()) {
Will Cassellaad9c3402022-09-08 22:52:53972 return ParseStatus(ParseStatusCode::kMalformedTag)
973 .AddCause(std::move(audio).error());
974 }
975 out.audio = std::move(audio).value();
976 }
977
Ted Meyer274348b2023-02-03 00:34:31978 // Extract the 'VIDEO' attribute
979 if (map.HasValue(XStreamInfTagAttribute::kVideo)) {
980 auto video =
981 types::ParseQuotedString(map.GetValue(XStreamInfTagAttribute::kVideo),
982 variable_dict, sub_buffer);
983 if (!video.has_value()) {
984 return ParseStatus(ParseStatusCode::kMalformedTag)
985 .AddCause(std::move(video).error());
986 }
987 out.video = std::move(video).value();
988 }
989
Will Cassella58c7fc632022-04-06 18:48:39990 return out;
991}
992
Will Cassella5343f8f2022-07-20 00:11:33993// static
Will Cassella0412e58a2022-07-20 21:35:38994ParseStatus::Or<InfTag> InfTag::Parse(TagItem tag) {
995 DCHECK(tag.GetName() == ToTagName(InfTag::kName));
996
997 if (!tag.GetContent()) {
998 return ParseStatusCode::kMalformedTag;
999 }
1000 auto content = *tag.GetContent();
1001
1002 // Inf tags have the form #EXTINF:<duration>,[<title>]
1003 // Find the comma.
1004 auto comma = content.Str().find_first_of(',');
Ted Meyerbc1dcd02023-07-25 18:56:261005 SourceString duration_str = content;
1006 SourceString title_str = content;
Ted Meyerda164052024-03-04 05:46:241007 if (comma == std::string_view::npos) {
Ted Meyerbc1dcd02023-07-25 18:56:261008 // While the HLS spec does require commas at the end of inf tags, it's
1009 // incredibly common for sites to elide the comma if there is no title
1010 // attribute present. In this case, we should assert that there is at least
1011 // a trailing newline, and then strip it to generate a nameless tag.
1012 title_str = content.Substr(0, 0);
1013 if (*content.Str().end() != '\n') {
1014 duration_str = content;
1015 } else {
1016 duration_str = content.Substr(0, content.Str().length() - 1);
1017 }
1018 } else {
1019 duration_str = content.Substr(0, comma);
1020 title_str = content.Substr(comma + 1);
Will Cassella23cdc9a12022-06-15 23:03:261021 }
1022
Will Cassella0412e58a2022-07-20 21:35:381023 // Extract duration
Alison Gale59c007a2024-04-20 03:05:401024 // TODO(crbug.com/40210233): Below version 3 this should be rounded to an
Will Cassella0412e58a2022-07-20 21:35:381025 // integer
1026 auto duration_result =
1027 types::ParseDecimalFloatingPoint(duration_str.SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:251028 if (!duration_result.has_value()) {
Will Cassella23cdc9a12022-06-15 23:03:261029 return ParseStatus(ParseStatusCode::kMalformedTag)
1030 .AddCause(std::move(duration_result).error());
1031 }
Will Cassella0412e58a2022-07-20 21:35:381032 const auto duration = base::Seconds(std::move(duration_result).value());
Will Cassella23cdc9a12022-06-15 23:03:261033
Will Cassella23cdc9a12022-06-15 23:03:261034 if (duration.is_max()) {
1035 return ParseStatusCode::kValueOverflowsTimeDelta;
1036 }
1037
Will Cassella0412e58a2022-07-20 21:35:381038 return InfTag{.duration = duration, .title = title_str};
Will Cassellad7bc2c02022-05-11 00:52:021039}
Will Cassella3ff524e2022-04-21 21:52:131040
Will Cassella5343f8f2022-07-20 00:11:331041// static
Will Cassella0412e58a2022-07-20 21:35:381042ParseStatus::Or<XBitrateTag> XBitrateTag::Parse(TagItem tag) {
1043 return ParseDecimalIntegerTag(tag, &XBitrateTag::bitrate);
1044}
1045
1046// static
1047ParseStatus::Or<XByteRangeTag> XByteRangeTag::Parse(TagItem tag) {
1048 DCHECK(tag.GetName() == ToTagName(XByteRangeTag::kName));
Will Cassella5c7327e2022-05-26 19:28:461049 if (!tag.GetContent().has_value()) {
1050 return ParseStatusCode::kMalformedTag;
1051 }
1052
Ted Meyer0aa70612024-06-10 23:16:191053 auto range = types::parsing::ByteRangeExpression::Parse(
Will Cassella0412e58a2022-07-20 21:35:381054 tag.GetContent()->SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:251055 if (!range.has_value()) {
Will Cassella5c7327e2022-05-26 19:28:461056 return ParseStatus(ParseStatusCode::kMalformedTag)
Will Cassella0412e58a2022-07-20 21:35:381057 .AddCause(std::move(range).error());
Will Cassella5c7327e2022-05-26 19:28:461058 }
1059
Will Cassella0412e58a2022-07-20 21:35:381060 return XByteRangeTag{.range = std::move(range).value()};
1061}
Will Cassella23cdc9a12022-06-15 23:03:261062
Will Cassella0412e58a2022-07-20 21:35:381063// static
1064ParseStatus::Or<XDiscontinuityTag> XDiscontinuityTag::Parse(TagItem tag) {
1065 return ParseEmptyTag<XDiscontinuityTag>(tag);
1066}
Will Cassella23cdc9a12022-06-15 23:03:261067
Will Cassella0412e58a2022-07-20 21:35:381068// static
1069ParseStatus::Or<XDiscontinuitySequenceTag> XDiscontinuitySequenceTag::Parse(
1070 TagItem tag) {
1071 return ParseDecimalIntegerTag(tag, &XDiscontinuitySequenceTag::number);
1072}
Will Cassella23cdc9a12022-06-15 23:03:261073
Will Cassella0412e58a2022-07-20 21:35:381074// static
1075ParseStatus::Or<XEndListTag> XEndListTag::Parse(TagItem tag) {
1076 return ParseEmptyTag<XEndListTag>(tag);
1077}
Will Cassella5c7327e2022-05-26 19:28:461078
Will Cassella0412e58a2022-07-20 21:35:381079// static
1080ParseStatus::Or<XGapTag> XGapTag::Parse(TagItem tag) {
1081 return ParseEmptyTag<XGapTag>(tag);
1082}
1083
1084// static
1085ParseStatus::Or<XIFramesOnlyTag> XIFramesOnlyTag::Parse(TagItem tag) {
1086 return ParseEmptyTag<XIFramesOnlyTag>(tag);
1087}
1088
1089// static
Will Cassellafc185a692022-07-21 00:29:511090ParseStatus::Or<XMapTag> XMapTag::Parse(
1091 TagItem tag,
1092 const VariableDictionary& variable_dict,
1093 VariableDictionary::SubstitutionBuffer& sub_buffer) {
1094 DCHECK(tag.GetName() == ToTagName(XMapTag::kName));
1095 if (!tag.GetContent().has_value()) {
1096 return ParseStatusCode::kMalformedTag;
1097 }
1098
1099 // Parse the attribute-list
1100 TypedAttributeMap<XMapTagAttribute> map;
1101 types::AttributeListIterator iter(*tag.GetContent());
1102 auto map_result = map.FillUntilError(&iter);
1103
1104 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1105 return ParseStatus(ParseStatusCode::kMalformedTag)
1106 .AddCause(std::move(map_result));
1107 }
1108
Arthur Sonzogni581b9e82024-01-30 17:25:151109 std::optional<ResolvedSourceString> uri;
Will Cassellafc185a692022-07-21 00:29:511110 if (map.HasValue(XMapTagAttribute::kUri)) {
1111 auto result = types::ParseQuotedString(map.GetValue(XMapTagAttribute::kUri),
1112 variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:251113 if (!result.has_value()) {
Will Cassellafc185a692022-07-21 00:29:511114 return ParseStatus(ParseStatusCode::kMalformedTag)
1115 .AddCause(std::move(result).error());
1116 }
1117
1118 uri = std::move(result).value();
1119 } else {
1120 return ParseStatusCode::kMalformedTag;
1121 }
1122
Ted Meyer0aa70612024-06-10 23:16:191123 std::optional<types::parsing::ByteRangeExpression> byte_range;
Will Cassellafc185a692022-07-21 00:29:511124 if (map.HasValue(XMapTagAttribute::kByteRange)) {
1125 auto result =
1126 types::ParseQuotedString(map.GetValue(XMapTagAttribute::kByteRange),
1127 variable_dict, sub_buffer)
Ted Meyer0aa70612024-06-10 23:16:191128 .MapValue(types::parsing::ByteRangeExpression::Parse);
Ted Meyer0eb6fcc2022-10-21 03:21:251129 if (!result.has_value()) {
Will Cassellafc185a692022-07-21 00:29:511130 return ParseStatus(ParseStatusCode::kMalformedTag)
1131 .AddCause(std::move(result).error());
1132 }
1133
1134 byte_range = std::move(result).value();
1135 }
1136
1137 return XMapTag{.uri = uri.value(), .byte_range = byte_range};
1138}
1139
1140// static
Will Cassella0412e58a2022-07-20 21:35:381141ParseStatus::Or<XMediaSequenceTag> XMediaSequenceTag::Parse(TagItem tag) {
1142 return ParseDecimalIntegerTag(tag, &XMediaSequenceTag::number);
Will Cassella5c7327e2022-05-26 19:28:461143}
1144
Will Cassella5343f8f2022-07-20 00:11:331145// static
Will Cassella53a6da12022-07-14 00:53:361146ParseStatus::Or<XPartTag> XPartTag::Parse(
1147 TagItem tag,
Ted Meyer38a01192024-06-06 22:53:441148 const VariableDictionary& vars,
1149 VariableDictionary::SubstitutionBuffer& subs) {
Will Cassella53a6da12022-07-14 00:53:361150 DCHECK(tag.GetName() == ToTagName(XPartTag::kName));
1151 if (!tag.GetContent().has_value()) {
1152 return ParseStatusCode::kMalformedTag;
1153 }
1154
1155 TypedAttributeMap<XPartTagAttribute> map;
1156 types::AttributeListIterator iter(*tag.GetContent());
1157 auto map_result = map.FillUntilError(&iter);
1158
1159 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1160 return ParseStatus(ParseStatusCode::kMalformedTag)
1161 .AddCause(std::move(map_result));
1162 }
1163
Ted Meyer0aa70612024-06-10 23:16:191164 auto uri = ParseField<ResolvedSourceString>(
1165 XPartTagAttribute::kUri, map,
1166 &types::parsing::Quoted<types::parsing::RawStr>::ParseWithSubstitution,
1167 vars, subs);
Ted Meyer38a01192024-06-06 22:53:441168 RETURN_IF_ERROR(uri);
Will Cassella53a6da12022-07-14 00:53:361169
Ted Meyer0aa70612024-06-10 23:16:191170 auto duration = ParseField<base::TimeDelta>(
1171 XPartTagAttribute::kDuration, map,
1172 &types::parsing::TimeDelta::ParseWithoutSubstitution);
Ted Meyer38a01192024-06-06 22:53:441173 RETURN_IF_ERROR(duration);
Will Cassella53a6da12022-07-14 00:53:361174
Ted Meyer0aa70612024-06-10 23:16:191175 auto byte_range =
1176 ParseField<std::optional<types::parsing::ByteRangeExpression>>(
1177 XPartTagAttribute::kByteRange, map,
1178 &types::parsing::Quoted<
1179 types::parsing::ByteRangeExpression>::ParseWithSubstitution,
1180 vars, subs);
Ted Meyer38a01192024-06-06 22:53:441181 RETURN_IF_ERROR(byte_range);
Will Cassella53a6da12022-07-14 00:53:361182
Ted Meyer38a01192024-06-06 22:53:441183 auto independent = ParseField<std::optional<bool>>(
Ted Meyer0aa70612024-06-10 23:16:191184 XPartTagAttribute::kIndependent, map,
1185 &types::parsing::YesOrNo::ParseWithoutSubstitution);
Ted Meyer38a01192024-06-06 22:53:441186 RETURN_IF_ERROR(independent);
Will Cassella53a6da12022-07-14 00:53:361187
Ted Meyer0aa70612024-06-10 23:16:191188 auto gap = ParseField<std::optional<bool>>(
1189 XPartTagAttribute::kGap, map,
1190 &types::parsing::YesOrNo::ParseWithoutSubstitution);
Ted Meyer38a01192024-06-06 22:53:441191 RETURN_IF_ERROR(gap);
Will Cassella53a6da12022-07-14 00:53:361192
Ted Meyer38a01192024-06-06 22:53:441193 return XPartTag{.uri = std::move(uri).value(),
1194 .duration = std::move(duration).value(),
1195 .byte_range = std::move(byte_range).value(),
1196 .independent = (*independent).value_or(false),
1197 .gap = (*gap).value_or(false)};
Will Cassella53a6da12022-07-14 00:53:361198}
1199
Will Cassella5343f8f2022-07-20 00:11:331200// static
Will Cassella0412e58a2022-07-20 21:35:381201ParseStatus::Or<XPartInfTag> XPartInfTag::Parse(TagItem tag) {
1202 DCHECK(tag.GetName() == ToTagName(XPartInfTag::kName));
1203 if (!tag.GetContent().has_value()) {
1204 return ParseStatusCode::kMalformedTag;
1205 }
1206
1207 // Parse the attribute-list
1208 TypedAttributeMap<XPartInfTagAttribute> map;
1209 types::AttributeListIterator iter(*tag.GetContent());
1210 auto map_result = map.FillUntilError(&iter);
1211
1212 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1213 return ParseStatus(ParseStatusCode::kMalformedTag)
1214 .AddCause(std::move(map_result));
1215 }
1216
1217 // Extract the 'PART-TARGET' attribute
1218 base::TimeDelta part_target;
1219 if (map.HasValue(XPartInfTagAttribute::kPartTarget)) {
1220 auto result = types::ParseDecimalFloatingPoint(
1221 map.GetValue(XPartInfTagAttribute::kPartTarget)
1222 .SkipVariableSubstitution());
1223
Ted Meyer0eb6fcc2022-10-21 03:21:251224 if (!result.has_value()) {
Will Cassella0412e58a2022-07-20 21:35:381225 return ParseStatus(ParseStatusCode::kMalformedTag)
1226 .AddCause(std::move(result).error());
1227 }
1228
1229 part_target = base::Seconds(std::move(result).value());
1230
1231 if (part_target.is_max()) {
1232 return ParseStatusCode::kValueOverflowsTimeDelta;
1233 }
1234 } else {
1235 return ParseStatusCode::kMalformedTag;
1236 }
1237
1238 return XPartInfTag{.target_duration = part_target};
1239}
1240
1241// static
1242ParseStatus::Or<XPlaylistTypeTag> XPlaylistTypeTag::Parse(TagItem tag) {
1243 DCHECK(tag.GetName() == ToTagName(XPlaylistTypeTag::kName));
1244
1245 // This tag requires content
1246 if (!tag.GetContent().has_value() || tag.GetContent()->Empty()) {
1247 return ParseStatusCode::kMalformedTag;
1248 }
1249
1250 if (tag.GetContent()->Str() == "EVENT") {
1251 return XPlaylistTypeTag{.type = PlaylistType::kEvent};
1252 }
1253 if (tag.GetContent()->Str() == "VOD") {
1254 return XPlaylistTypeTag{.type = PlaylistType::kVOD};
1255 }
1256
1257 return ParseStatusCode::kUnknownPlaylistType;
1258}
1259
1260// static
Will Cassellacfa1b1b42022-06-17 20:50:521261ParseStatus::Or<XServerControlTag> XServerControlTag::Parse(TagItem tag) {
1262 DCHECK(tag.GetName() == ToTagName(XServerControlTag::kName));
1263 if (!tag.GetContent().has_value()) {
1264 return ParseStatusCode::kMalformedTag;
1265 }
1266
1267 // Parse the attribute-list
1268 TypedAttributeMap<XServerControlTagAttribute> map;
1269 types::AttributeListIterator iter(*tag.GetContent());
1270 auto map_result = map.FillUntilError(&iter);
1271
1272 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1273 return ParseStatus(ParseStatusCode::kMalformedTag)
1274 .AddCause(std::move(map_result));
1275 }
1276
1277 // Extract the 'CAN-SKIP-UNTIL' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:151278 std::optional<base::TimeDelta> can_skip_until;
Will Cassellacfa1b1b42022-06-17 20:50:521279 if (map.HasValue(XServerControlTagAttribute::kCanSkipUntil)) {
1280 auto result = types::ParseDecimalFloatingPoint(
Will Cassella0d3ed6a2022-06-27 19:39:291281 map.GetValue(XServerControlTagAttribute::kCanSkipUntil)
1282 .SkipVariableSubstitution());
Will Cassellacfa1b1b42022-06-17 20:50:521283
Ted Meyer0eb6fcc2022-10-21 03:21:251284 if (!result.has_value()) {
Will Cassellacfa1b1b42022-06-17 20:50:521285 return ParseStatus(ParseStatusCode::kMalformedTag)
1286 .AddCause(std::move(result).error());
1287 }
1288
1289 can_skip_until = base::Seconds(std::move(result).value());
1290
1291 if (can_skip_until->is_max()) {
1292 return ParseStatusCode::kValueOverflowsTimeDelta;
1293 }
1294 }
1295
1296 // Extract the 'CAN-SKIP-DATERANGES' attribute
1297 bool can_skip_dateranges = false;
1298 if (map.HasValue(XServerControlTagAttribute::kCanSkipDateRanges)) {
1299 if (map.GetValue(XServerControlTagAttribute::kCanSkipDateRanges).Str() ==
1300 "YES") {
1301 // The existence of this attribute requires the 'CAN-SKIP-UNTIL'
1302 // attribute.
1303 if (!can_skip_until.has_value()) {
1304 return ParseStatusCode::kMalformedTag;
1305 }
1306
1307 can_skip_dateranges = true;
1308 }
1309 }
1310
1311 // Extract the 'HOLD-BACK' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:151312 std::optional<base::TimeDelta> hold_back;
Will Cassellacfa1b1b42022-06-17 20:50:521313 if (map.HasValue(XServerControlTagAttribute::kHoldBack)) {
1314 auto result = types::ParseDecimalFloatingPoint(
Will Cassella0d3ed6a2022-06-27 19:39:291315 map.GetValue(XServerControlTagAttribute::kHoldBack)
1316 .SkipVariableSubstitution());
Will Cassellacfa1b1b42022-06-17 20:50:521317
Ted Meyer0eb6fcc2022-10-21 03:21:251318 if (!result.has_value()) {
Will Cassellacfa1b1b42022-06-17 20:50:521319 return ParseStatus(ParseStatusCode::kMalformedTag)
1320 .AddCause(std::move(result).error());
1321 }
1322
1323 hold_back = base::Seconds(std::move(result).value());
1324
1325 if (hold_back->is_max()) {
1326 return ParseStatusCode::kValueOverflowsTimeDelta;
1327 }
1328 }
1329
1330 // Extract the 'PART-HOLD-BACK' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:151331 std::optional<base::TimeDelta> part_hold_back;
Will Cassellacfa1b1b42022-06-17 20:50:521332 if (map.HasValue(XServerControlTagAttribute::kPartHoldBack)) {
1333 auto result = types::ParseDecimalFloatingPoint(
Will Cassella0d3ed6a2022-06-27 19:39:291334 map.GetValue(XServerControlTagAttribute::kPartHoldBack)
1335 .SkipVariableSubstitution());
Will Cassellacfa1b1b42022-06-17 20:50:521336
Ted Meyer0eb6fcc2022-10-21 03:21:251337 if (!result.has_value()) {
Will Cassellacfa1b1b42022-06-17 20:50:521338 return ParseStatus(ParseStatusCode::kMalformedTag)
1339 .AddCause(std::move(result).error());
1340 }
1341
1342 part_hold_back = base::Seconds(std::move(result).value());
1343
1344 if (part_hold_back->is_max()) {
1345 return ParseStatusCode::kValueOverflowsTimeDelta;
1346 }
1347 }
1348
1349 // Extract the 'CAN-BLOCK-RELOAD' attribute
1350 bool can_block_reload = false;
1351 if (map.HasValue(XServerControlTagAttribute::kCanBlockReload)) {
1352 if (map.GetValue(XServerControlTagAttribute::kCanBlockReload).Str() ==
1353 "YES") {
1354 can_block_reload = true;
1355 }
1356 }
1357
1358 return XServerControlTag{
1359 .skip_boundary = can_skip_until,
1360 .can_skip_dateranges = can_skip_dateranges,
1361 .hold_back = hold_back,
1362 .part_hold_back = part_hold_back,
1363 .can_block_reload = can_block_reload,
1364 };
1365}
1366
Will Cassella5343f8f2022-07-20 00:11:331367// static
Will Cassella0412e58a2022-07-20 21:35:381368ParseStatus::Or<XTargetDurationTag> XTargetDurationTag::Parse(TagItem tag) {
1369 DCHECK(tag.GetName() == ToTagName(XTargetDurationTag::kName));
Will Cassella6fa09342022-05-24 23:36:101370 if (!tag.GetContent().has_value()) {
1371 return ParseStatusCode::kMalformedTag;
1372 }
1373
Will Cassella0412e58a2022-07-20 21:35:381374 auto duration_result = types::ParseDecimalInteger(
1375 tag.GetContent().value().SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:251376 if (!duration_result.has_value()) {
Will Cassella6fa09342022-05-24 23:36:101377 return ParseStatus(ParseStatusCode::kMalformedTag)
Will Cassella0412e58a2022-07-20 21:35:381378 .AddCause(std::move(duration_result).error());
Will Cassella6fa09342022-05-24 23:36:101379 }
1380
Will Cassella0412e58a2022-07-20 21:35:381381 auto duration = base::Seconds(std::move(duration_result).value());
1382 if (duration.is_max()) {
1383 return ParseStatusCode::kValueOverflowsTimeDelta;
1384 }
Will Cassella6fa09342022-05-24 23:36:101385
Will Cassella0412e58a2022-07-20 21:35:381386 return XTargetDurationTag{.duration = duration};
Will Cassella20b2d7382022-05-25 00:25:191387}
1388
Ted Meyerf7566d32023-12-06 02:31:121389XSkipTag::XSkipTag() = default;
1390XSkipTag::~XSkipTag() = default;
1391XSkipTag::XSkipTag(const XSkipTag&) = default;
1392XSkipTag::XSkipTag(XSkipTag&&) = default;
1393
1394ParseStatus::Or<XSkipTag> XSkipTag::Parse(
1395 TagItem tag,
1396 const VariableDictionary& variable_dict,
1397 VariableDictionary::SubstitutionBuffer& sub_buffer) {
1398 DCHECK(tag.GetName() == ToTagName(XSkipTag::kName));
1399 if (!tag.GetContent().has_value()) {
1400 return ParseStatusCode::kMalformedTag;
1401 }
1402
1403 XSkipTag out;
1404 TypedAttributeMap<XSkipTagAttribute> map;
1405 types::AttributeListIterator iter(*tag.GetContent());
1406 auto map_result = map.FillUntilError(&iter);
1407
1408 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1409 return ParseStatus(ParseStatusCode::kMalformedTag)
1410 .AddCause(std::move(map_result));
1411 }
1412
1413 if (!map.HasValue(XSkipTagAttribute::kSkippedSegments)) {
1414 return ParseStatusCode::kMalformedTag;
1415 }
1416
1417 auto skip_result = types::ParseDecimalInteger(
1418 map.GetValue(XSkipTagAttribute::kSkippedSegments)
1419 .SkipVariableSubstitution());
1420
1421 if (!skip_result.has_value()) {
1422 return ParseStatus(ParseStatusCode::kMalformedTag)
1423 .AddCause(std::move(skip_result).error());
1424 }
1425
1426 out.skipped_segments = std::move(skip_result).value();
1427
1428 if (map.HasValue(XSkipTagAttribute::kRecentlyRemovedDateranges)) {
1429 // TODO(bug/314833987): Should this list have substitution?
1430 auto removed_result = types::ParseQuotedString(
1431 map.GetValue(XSkipTagAttribute::kRecentlyRemovedDateranges),
1432 variable_dict, sub_buffer, /*allow_empty=*/true);
1433 if (!removed_result.has_value()) {
1434 return ParseStatus(ParseStatusCode::kMalformedTag)
1435 .AddCause(std::move(removed_result).error());
1436 }
1437
1438 auto tab_joined_daterange_ids = std::move(removed_result).value();
1439 std::vector<std::string> removed_dateranges = {};
1440
1441 while (!tab_joined_daterange_ids.Empty()) {
1442 const auto daterange_id = tab_joined_daterange_ids.ConsumeDelimiter('\t');
1443 if (daterange_id.Empty()) {
1444 return ParseStatusCode::kMalformedTag;
1445 }
1446 // TODO(bug/314833987): What type should this be parsed into?
1447 removed_dateranges.emplace_back(daterange_id.Str());
1448 }
1449 out.recently_removed_dateranges = std::move(removed_dateranges);
1450 }
1451
1452 return out;
1453}
1454
Ted Meyere55bc2b2024-02-24 00:31:251455ParseStatus::Or<XRenditionReportTag> XRenditionReportTag::Parse(
1456 TagItem tag,
1457 const VariableDictionary& variable_dict,
1458 VariableDictionary::SubstitutionBuffer& sub_buffer) {
1459 CHECK(tag.GetName() == ToTagName(XRenditionReportTag::kName));
1460 if (!tag.GetContent().has_value()) {
1461 return ParseStatusCode::kMalformedTag;
1462 }
1463 XRenditionReportTag out;
1464 TypedAttributeMap<XRenditionReportTagAttribute> map;
1465 types::AttributeListIterator iter(*tag.GetContent());
1466 auto map_result = map.FillUntilError(&iter);
1467
1468 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1469 return std::move(map_result).AddHere();
1470 }
1471
1472 if (map.HasValue(XRenditionReportTagAttribute::kUri)) {
1473 auto uri_result = types::ParseQuotedString(
1474 map.GetValue(XRenditionReportTagAttribute::kUri), variable_dict,
1475 sub_buffer);
1476 if (!uri_result.has_value()) {
1477 return std::move(uri_result).error().AddHere();
1478 }
1479 out.uri = std::move(uri_result).value();
1480 }
1481
1482 if (map.HasValue(XRenditionReportTagAttribute::kLastMSN)) {
1483 auto msn_result = types::ParseDecimalInteger(
1484 map.GetValue(XRenditionReportTagAttribute::kLastMSN)
1485 .SkipVariableSubstitution());
1486 if (!msn_result.has_value()) {
1487 return std::move(msn_result).error().AddHere();
1488 }
1489 out.last_msn = std::move(msn_result).value();
1490 }
1491
1492 if (map.HasValue(XRenditionReportTagAttribute::kLastPart)) {
1493 auto part_result = types::ParseDecimalInteger(
1494 map.GetValue(XRenditionReportTagAttribute::kLastPart)
1495 .SkipVariableSubstitution());
1496 if (!part_result.has_value()) {
1497 return std::move(part_result).error().AddHere();
1498 }
1499 out.last_part = std::move(part_result).value();
1500 }
1501
1502 return out;
1503}
1504
1505ParseStatus::Or<XProgramDateTimeTag> XProgramDateTimeTag::Parse(TagItem tag) {
1506 return ParseISO8601DateTimeTag(tag, &XProgramDateTimeTag::time);
1507}
1508
Ted Meyer54a32922024-06-14 21:51:161509template <typename Attrs>
1510ParseStatus::Or<TypedAttributeMap<Attrs>> RequireNonEmptyMap(
1511 std::optional<SourceString> content) {
1512 if (!content.has_value()) {
1513 return ParseStatusCode::kMalformedTag;
1514 }
1515
1516 TypedAttributeMap<Attrs> map;
1517 types::AttributeListIterator iter(*content);
1518 auto map_result = map.FillUntilError(&iter);
1519
1520 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1521 return ParseStatus(ParseStatusCode::kMalformedTag)
1522 .AddCause(std::move(map_result));
1523 }
1524
1525 return map;
1526}
1527
Ted Meyerb07c5d22024-06-25 19:16:181528namespace {
1529
1530constexpr char const* kMethodNone = "NONE";
1531constexpr char const* kMethodAES128 = "AES-128";
1532constexpr char const* kMethodAES256 = "AES-256";
1533constexpr char const* kMethodSampleAES = "SAMPLE-AES";
1534constexpr char const* kMethodSampleAESCTR = "SAMPLE-AES-CTR";
1535constexpr char const* kMethodSampleAESCENC = "SAMPLE-AES-CENC";
1536constexpr char const* kMethodISO230017 = "ISO-23001-7";
1537
1538constexpr char const* kFmtClearkey = "org.w3.clearkey";
1539constexpr char const* kFmtWidevine =
1540 "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
1541constexpr char const* kFmtIdentity = "identity";
1542
1543ParseStatus::Or<XKeyTagMethod> RecognizeMethod(SourceString content) {
1544 if (content.Str() == kMethodNone) {
1545 return XKeyTagMethod::kNone;
1546 }
1547 if (content.Str() == kMethodAES128) {
1548 return XKeyTagMethod::kAES128;
1549 } else if (content.Str() == kMethodAES256) {
1550 return XKeyTagMethod::kAES256;
1551 } else if (content.Str() == kMethodSampleAES) {
1552 return XKeyTagMethod::kSampleAES;
1553 } else if (content.Str() == kMethodSampleAESCTR) {
1554 return XKeyTagMethod::kSampleAESCTR;
1555 } else if (content.Str() == kMethodSampleAESCENC) {
1556 return XKeyTagMethod::kSampleAESCENC;
1557 } else if (content.Str() == kMethodISO230017) {
1558 return XKeyTagMethod::kISO230017;
1559 } else {
1560 return ParseStatusCode::kMalformedTag;
1561 }
1562}
1563
1564ParseStatus::Or<XKeyTagKeyFormat> RecognizeFormat(
1565 std::optional<ResolvedSourceString> content) {
1566 if (!content.has_value()) {
1567 return XKeyTagKeyFormat::kIdentity;
1568 } else if (content->Str() == kFmtClearkey) {
1569 return XKeyTagKeyFormat::kClearKey;
1570 } else if (content->Str() == kFmtWidevine) {
1571 return XKeyTagKeyFormat::kWidevine;
1572 } else if (content->Str() == kFmtIdentity) {
1573 return XKeyTagKeyFormat::kIdentity;
1574 } else {
1575 return XKeyTagKeyFormat::kUnsupported;
1576 }
1577}
1578
1579template <typename T>
1580ParseStatus::Or<T> ValidateKeyTag(
1581 XKeyTagMethod method,
1582 ResolvedSourceString uri,
1583 std::optional<XKeyTag::IVHex::Container> iv,
1584 XKeyTagKeyFormat keyformat,
1585 std::optional<ResolvedSourceString> keyformat_versions) {
1586 // Check for incompatible fields. Some methods _must not_ have an IV
1587 // present, while some formats only have a specific set of allowable
1588 // methods.
1589 if (iv.has_value()) {
1590 switch (method) {
1591 case XKeyTagMethod::kSampleAESCTR:
1592 case XKeyTagMethod::kSampleAESCENC:
1593 case XKeyTagMethod::kISO230017:
1594 return ParseStatusCode::kMalformedTag;
1595 default:
1596 break;
1597 }
1598 }
1599 switch (keyformat) {
1600 case XKeyTagKeyFormat::kIdentity:
1601 case XKeyTagKeyFormat::kUnsupported:
1602 break; // Identity and others always are ok.
1603 case XKeyTagKeyFormat::kClearKey:
1604 case XKeyTagKeyFormat::kWidevine:
1605 switch (method) {
1606 case XKeyTagMethod::kSampleAES:
1607 case XKeyTagMethod::kSampleAESCTR:
1608 case XKeyTagMethod::kSampleAESCENC:
1609 case XKeyTagMethod::kISO230017:
1610 break; // Acceptable methods for widevine and clearkey
1611 case XKeyTagMethod::kNone:
1612 case XKeyTagMethod::kAES128:
1613 case XKeyTagMethod::kAES256:
1614 return ParseStatusCode::kMalformedTag;
1615 }
1616 break;
1617 }
1618
1619 return T{.method = method,
1620 .uri = std::move(uri),
1621 .iv = std::move(iv),
1622 .keyformat = keyformat,
1623 .keyformat_versions = std::move(keyformat_versions)};
1624}
1625
1626template <typename T>
1627ParseStatus::Or<T> ParseKeyTag(TagItem tag,
1628 const VariableDictionary& vars,
1629 VariableDictionary::SubstitutionBuffer& subs) {
Ted Meyer54a32922024-06-14 21:51:161630 return RequireNonEmptyMap<XKeyTagAttribute>(tag.GetContent())
Ted Meyerb07c5d22024-06-25 19:16:181631 .MapValue([&](auto map) -> ParseStatus::Or<T> {
1632 auto enc_method = ParseField<XKeyTagMethod>(XKeyTagAttribute::kMethod,
1633 map, &RecognizeMethod);
Ted Meyer54a32922024-06-14 21:51:161634 RETURN_IF_ERROR(enc_method);
1635
Ted Meyerb07c5d22024-06-25 19:16:181636 if constexpr (T::kAllowEmptyMethod) {
1637 if (*enc_method == XKeyTagMethod::kNone) {
1638 if (map.Size() != 1) {
1639 return ParseStatusCode::kMalformedTag;
1640 }
1641 return T{.method = *enc_method};
Ted Meyer54a32922024-06-14 21:51:161642 }
Ted Meyer54a32922024-06-14 21:51:161643 }
1644
1645 auto enc_uri = ParseField<ResolvedSourceString>(
1646 XKeyTagAttribute::kUri, map,
1647 &types::parsing::Quoted<
1648 types::parsing::RawStr>::ParseWithSubstitution,
1649 vars, subs);
1650 RETURN_IF_ERROR(enc_uri);
1651
1652 auto enc_iv = ParseField<std::optional<XKeyTag::IVHex::Container>>(
1653 XKeyTagAttribute::kIv, map,
1654 &XKeyTag::IVHex::ParseWithoutSubstitution);
1655 RETURN_IF_ERROR(enc_iv);
1656
Ted Meyer54a32922024-06-14 21:51:161657 auto enc_keyformat =
1658 ParseField<std::optional<ResolvedSourceString>>(
1659 XKeyTagAttribute::kKeyFormat, map,
1660 &types::parsing::Quoted<
1661 types::parsing::RawStr>::ParseWithoutSubstitution)
Ted Meyerb07c5d22024-06-25 19:16:181662 .MapValue(&RecognizeFormat);
Ted Meyer54a32922024-06-14 21:51:161663 RETURN_IF_ERROR(enc_keyformat);
1664
1665 auto enc_keyformat_versions =
1666 ParseField<std::optional<ResolvedSourceString>>(
1667 XKeyTagAttribute::kKeyFormatVersions, map,
1668 &types::parsing::RawStr::ParseWithoutSubstitution);
1669 RETURN_IF_ERROR(enc_keyformat_versions);
1670
Ted Meyerb07c5d22024-06-25 19:16:181671 return ValidateKeyTag<T>(*enc_method, *enc_uri, *enc_iv, *enc_keyformat,
1672 *enc_keyformat_versions);
Ted Meyer54a32922024-06-14 21:51:161673 });
1674}
1675
Ted Meyerb07c5d22024-06-25 19:16:181676} // namespace
1677
1678ParseStatus::Or<XKeyTag> XKeyTag::Parse(
1679 TagItem tag,
1680 const VariableDictionary& vars,
1681 VariableDictionary::SubstitutionBuffer& subs) {
1682 DCHECK(tag.GetName() == ToTagName(XKeyTag::kName));
1683 return ParseKeyTag<XKeyTag>(tag, vars, subs);
1684}
1685
Ted Meyer92c6afd2024-06-15 00:25:591686ParseStatus::Or<XSessionKeyTag> XSessionKeyTag::Parse(
1687 TagItem tag,
1688 const VariableDictionary& vars,
1689 VariableDictionary::SubstitutionBuffer& subs) {
1690 DCHECK(tag.GetName() == ToTagName(XSessionKeyTag::kName));
Ted Meyerb07c5d22024-06-25 19:16:181691 return ParseKeyTag<XSessionKeyTag>(tag, vars, subs);
Ted Meyer92c6afd2024-06-15 00:25:591692}
1693
Ted Meyer38a01192024-06-06 22:53:441694#undef RETURN_IF_ERROR
1695
Will Cassellad9d31172022-03-04 20:52:251696} // namespace media::hls