[go: nahoru, domu]

blob: e70183ac090d7149babdf3dfdbc6c76ca65d8cdf [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/formats/hls/tags.h"
#include <cstddef>
#include <type_traits>
#include <utility>
#include "base/logging.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "media/base/mime_util.h"
#include "media/formats/hls/items.h"
#include "media/formats/hls/parse_status.h"
#include "media/formats/hls/variable_dictionary.h"
namespace media::hls {
namespace {
template <typename T>
ParseStatus::Or<T> ParseEmptyTag(TagItem tag) {
DCHECK(tag.GetName() == ToTagName(T::kName));
if (tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
return T{};
}
template <typename T>
ParseStatus::Or<T> ParseDecimalIntegerTag(TagItem tag,
types::DecimalInteger T::*field) {
DCHECK(tag.GetName() == ToTagName(T::kName));
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
auto value =
types::ParseDecimalInteger(tag.GetContent()->SkipVariableSubstitution());
if (!value.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(value).error());
}
T out;
out.*field = std::move(value).value();
return out;
}
template <typename T>
ParseStatus::Or<T> ParseISO8601DateTimeTag(TagItem tag, base::Time T::*field) {
CHECK(tag.GetName() == ToTagName(T::kName));
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
const auto content = tag.GetContent()->SkipVariableSubstitution().Str();
std::string content_nullterm = std::string(content);
T out;
base::Time time;
if (base::Time::FromString(content_nullterm.c_str(), &time)) {
out.*field = time;
} else {
return ParseStatusCode::kMalformedTag;
}
return out;
}
// Attributes expected in `EXT-X-DEFINE` tag contents.
// These must remain sorted alphabetically.
enum class XDefineTagAttribute {
kImport,
kName,
kValue,
kMaxValue = kValue,
};
constexpr std::string_view GetAttributeName(XDefineTagAttribute attribute) {
switch (attribute) {
case XDefineTagAttribute::kImport:
return "IMPORT";
case XDefineTagAttribute::kName:
return "NAME";
case XDefineTagAttribute::kValue:
return "VALUE";
}
NOTREACHED_NORETURN();
}
// Attributes expected in `EXT-X-MEDIA` tag contents.
// These must remain sorted alphabetically.
enum class XMediaTagAttribute {
kAssocLanguage,
kAutoselect,
kChannels,
kCharacteristics,
kDefault,
kForced,
kGroupId,
kInstreamId,
kLanguage,
kName,
kStableRenditionId,
kType,
kUri,
kMaxValue = kUri,
};
constexpr std::string_view GetAttributeName(XMediaTagAttribute attribute) {
switch (attribute) {
case XMediaTagAttribute::kAssocLanguage:
return "ASSOC-LANGUAGE";
case XMediaTagAttribute::kAutoselect:
return "AUTOSELECT";
case XMediaTagAttribute::kChannels:
return "CHANNELS";
case XMediaTagAttribute::kCharacteristics:
return "CHARACTERISTICS";
case XMediaTagAttribute::kDefault:
return "DEFAULT";
case XMediaTagAttribute::kForced:
return "FORCED";
case XMediaTagAttribute::kGroupId:
return "GROUP-ID";
case XMediaTagAttribute::kInstreamId:
return "INSTREAM-ID";
case XMediaTagAttribute::kLanguage:
return "LANGUAGE";
case XMediaTagAttribute::kName:
return "NAME";
case XMediaTagAttribute::kStableRenditionId:
return "STABLE-RENDITION-ID";
case XMediaTagAttribute::kType:
return "TYPE";
case XMediaTagAttribute::kUri:
return "URI";
}
NOTREACHED_NORETURN();
}
// Attributes expected in `EXT-X-STREAM-INF` tag contents.
// These must remain sorted alphabetically.
enum class XStreamInfTagAttribute {
kAudio,
kAverageBandwidth,
kBandwidth,
kCodecs,
kFrameRate,
kProgramId, // Ignored for backwards compatibility
kResolution,
kScore,
kVideo,
kMaxValue = kVideo,
};
constexpr std::string_view GetAttributeName(XStreamInfTagAttribute attribute) {
switch (attribute) {
case XStreamInfTagAttribute::kAudio:
return "AUDIO";
case XStreamInfTagAttribute::kVideo:
return "VIDEO";
case XStreamInfTagAttribute::kAverageBandwidth:
return "AVERAGE-BANDWIDTH";
case XStreamInfTagAttribute::kBandwidth:
return "BANDWIDTH";
case XStreamInfTagAttribute::kCodecs:
return "CODECS";
case XStreamInfTagAttribute::kFrameRate:
return "FRAME-RATE";
case XStreamInfTagAttribute::kProgramId:
return "PROGRAM-ID";
case XStreamInfTagAttribute::kResolution:
return "RESOLUTION";
case XStreamInfTagAttribute::kScore:
return "SCORE";
}
NOTREACHED_NORETURN();
}
// Attributes expected in `EXT-X-SKIP` tag contents.
// These must remain sorted alphabetically.
enum class XSkipTagAttribute {
kRecentlyRemovedDateranges,
kSkippedSegments,
kMaxValue = kSkippedSegments,
};
constexpr std::string_view GetAttributeName(XSkipTagAttribute attribute) {
switch (attribute) {
case XSkipTagAttribute::kRecentlyRemovedDateranges:
return "RECENTLY-REMOVED-DATERANGES";
case XSkipTagAttribute::kSkippedSegments:
return "SKIPPED-SEGMENTS";
}
NOTREACHED_NORETURN();
}
enum class XRenditionReportTagAttribute {
kLastMSN,
kLastPart,
kUri,
kMaxValue = kUri,
};
constexpr std::string_view GetAttributeName(XRenditionReportTagAttribute attr) {
switch (attr) {
case XRenditionReportTagAttribute::kUri:
return "URI";
case XRenditionReportTagAttribute::kLastMSN:
return "LAST-MSN";
case XRenditionReportTagAttribute::kLastPart:
return "LAST-PART";
}
NOTREACHED_NORETURN();
}
// Attributes expected in `EXT-X-MAP` tag contents.
// These must remain sorted alphabetically.
enum class XMapTagAttribute {
kByteRange,
kUri,
kMaxValue = kUri,
};
constexpr std::string_view GetAttributeName(XMapTagAttribute attribute) {
switch (attribute) {
case XMapTagAttribute::kByteRange:
return "BYTERANGE";
case XMapTagAttribute::kUri:
return "URI";
}
NOTREACHED_NORETURN();
}
// Attributes expected in `EXT-X-PART` tag contents.
// These must remain sorted alphabetically.
enum class XPartTagAttribute {
kByteRange,
kDuration,
kGap,
kIndependent,
kUri,
kMaxValue = kUri,
};
constexpr std::string_view GetAttributeName(XPartTagAttribute attribute) {
switch (attribute) {
case XPartTagAttribute::kByteRange:
return "BYTERANGE";
case XPartTagAttribute::kDuration:
return "DURATION";
case XPartTagAttribute::kGap:
return "GAP";
case XPartTagAttribute::kIndependent:
return "INDEPENDENT";
case XPartTagAttribute::kUri:
return "URI";
}
}
// Attributes expected in `EXT-X-PART-INF` tag contents.
// These must remain sorted alphabetically.
enum class XPartInfTagAttribute {
kPartTarget,
kMaxValue = kPartTarget,
};
constexpr std::string_view GetAttributeName(XPartInfTagAttribute attribute) {
switch (attribute) {
case XPartInfTagAttribute::kPartTarget:
return "PART-TARGET";
}
NOTREACHED_NORETURN();
}
// Attributes expected in `EXT-X-SERVER-CONTROL` tag contents.
// These must remain sorted alphabetically.
enum class XServerControlTagAttribute {
kCanBlockReload,
kCanSkipDateRanges,
kCanSkipUntil,
kHoldBack,
kPartHoldBack,
kMaxValue = kPartHoldBack,
};
constexpr std::string_view GetAttributeName(
XServerControlTagAttribute attribute) {
switch (attribute) {
case XServerControlTagAttribute::kCanBlockReload:
return "CAN-BLOCK-RELOAD";
case XServerControlTagAttribute::kCanSkipDateRanges:
return "CAN-SKIP-DATERANGES";
case XServerControlTagAttribute::kCanSkipUntil:
return "CAN-SKIP-UNTIL";
case XServerControlTagAttribute::kHoldBack:
return "HOLD-BACK";
case XServerControlTagAttribute::kPartHoldBack:
return "PART-HOLD-BACK";
}
NOTREACHED_NORETURN();
}
enum class XKeyTagAttribute {
kIv,
kKeyFormat,
kKeyFormatVersions,
kMethod,
kUri,
kMaxValue = kUri,
};
constexpr std::string_view GetAttributeName(XKeyTagAttribute attribute) {
switch (attribute) {
case XKeyTagAttribute::kIv:
return "IV";
case XKeyTagAttribute::kKeyFormat:
return "KEYFORMAT";
case XKeyTagAttribute::kKeyFormatVersions:
return "KEYFORMATVERSIONS";
case XKeyTagAttribute::kMethod:
return "METHOD";
case XKeyTagAttribute::kUri:
return "URI";
}
NOTREACHED_NORETURN();
}
template <typename T, size_t kLast>
constexpr bool IsAttributeEnumSorted(std::index_sequence<kLast>) {
return true;
}
template <typename T, size_t kLHS, size_t kRHS, size_t... kRest>
constexpr bool IsAttributeEnumSorted(
std::index_sequence<kLHS, kRHS, kRest...>) {
const auto lhs = GetAttributeName(static_cast<T>(kLHS));
const auto rhs = GetAttributeName(static_cast<T>(kRHS));
return lhs < rhs &&
IsAttributeEnumSorted<T>(std::index_sequence<kRHS, kRest...>{});
}
// Wraps `AttributeMap::MakeStorage` by mapping the (compile-time) sequence
// of size_t's to a sequence of the corresponding attribute enum names.
template <typename T, std::size_t... Indices>
constexpr std::array<types::AttributeMap::Item, sizeof...(Indices)>
MakeTypedAttributeMapStorage(std::index_sequence<Indices...> seq) {
static_assert(IsAttributeEnumSorted<T>(seq),
"Enum keys must be sorted alphabetically");
return types::AttributeMap::MakeStorage(
GetAttributeName(static_cast<T>(Indices))...);
}
// Helper for using AttributeMap with an enum of keys.
// The result of running `GetAttributeName` across `0..T::kMaxValue` (inclusive)
// must produced an ordered set of strings.
template <typename T>
struct TypedAttributeMap {
static_assert(std::is_enum<T>::value, "T must be an enum");
static_assert(std::is_same<decltype(GetAttributeName(std::declval<T>())),
std::string_view>::value,
"GetAttributeName must be overloaded for T to return a "
"std::string_view");
static constexpr size_t kNumKeys = static_cast<size_t>(T::kMaxValue) + 1;
TypedAttributeMap()
: attributes_(MakeTypedAttributeMapStorage<T>(
std::make_index_sequence<kNumKeys>())) {}
// Wraps `AttributeMap::FillUntilError` using the built-in storage object.
ParseStatus FillUntilError(types::AttributeListIterator* iter) {
types::AttributeMap map(attributes_);
return map.FillUntilError(iter);
}
// Returns whether the entry corresponding to the given key has a value.
bool HasValue(T key) const {
return attributes_[static_cast<size_t>(key)].second.has_value();
}
// Returns the value stored in the entry for the given key.
SourceString GetValue(T key) const {
return attributes_[static_cast<size_t>(key)].second.value();
}
size_t Size() const {
return std::count_if(attributes_.begin(), attributes_.end(),
[](const types::AttributeMap::Item& item) {
return item.second.has_value();
});
}
private:
std::array<types::AttributeMap::Item, kNumKeys> attributes_;
};
#define RETURN_IF_ERROR(var_not_expr) \
do { \
if (!var_not_expr.has_value()) { \
return std::move(var_not_expr).error().AddHere(); \
} \
} while (0)
template <template <typename...> typename SpecifiedContainer, typename Type>
struct is_specialization_of : std::false_type {};
template <template <typename...> typename SpecifiedContainer, typename... Types>
struct is_specialization_of<SpecifiedContainer, SpecifiedContainer<Types...>>
: std::true_type {};
// ParseField exists to help with the pattern where we need to parse some field
// from a dictionary of tag attributes where some of those fields are optional.
// It is designed to provide support for either optional or required fields,
// depending on the return-type specialization:
// When Result=std::optional<X>, ParseField will return a nullopt rather than
// an error if the `field_name` field can't be found in `map`.
// When Result=T (where T is not std::optional<X>), ParseField will fail with
// a ParseStatus if `field_name` is not found in map.
// A function pointer must also be provided which does the parsing from
// SourceString => Result, provided that the `field_name` key is present.
template <typename Result,
typename AttrEnum,
typename ParseFn,
typename... ParseFnArgs>
ParseStatus::Or<Result> ParseField(AttrEnum field_name,
TypedAttributeMap<AttrEnum> map,
ParseFn parser,
ParseFnArgs&&... args) {
if (map.HasValue(field_name)) {
auto maybe =
parser(map.GetValue(field_name), std::forward<ParseFnArgs>(args)...);
if (!maybe.has_value()) {
ParseStatus result = ParseStatusCode::kMalformedTag;
return std::move(result).AddCause(std::move(maybe).error());
}
return Result(std::move(maybe).value());
}
if constexpr (is_specialization_of<std::optional, Result>::value) {
return Result(std::nullopt);
}
return ParseStatusCode::kMalformedTag;
}
} // namespace
// static
ParseStatus::Or<M3uTag> M3uTag::Parse(TagItem tag) {
return ParseEmptyTag<M3uTag>(tag);
}
// static
XDefineTag XDefineTag::CreateDefinition(types::VariableName name,
std::string_view value) {
return XDefineTag{.name = name, .value = value};
}
// static
XDefineTag XDefineTag::CreateImport(types::VariableName name) {
return XDefineTag{.name = name, .value = std::nullopt};
}
// static
ParseStatus::Or<XDefineTag> XDefineTag::Parse(TagItem tag) {
DCHECK(tag.GetName() == ToTagName(XDefineTag::kName));
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
// Parse the attribute-list.
// Quoted strings in EXT-X-DEFINE tags are unique in that they aren't subject
// to variable substitution. For that reason, we use the
// `ParseQuotedStringWithoutSubstitution` function here.
TypedAttributeMap<XDefineTagAttribute> map;
types::AttributeListIterator iter(*tag.GetContent());
auto result = map.FillUntilError(&iter);
if (result.code() != ParseStatusCode::kReachedEOF) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result));
}
// "NAME" and "IMPORT" are mutually exclusive
if (map.HasValue(XDefineTagAttribute::kName) &&
map.HasValue(XDefineTagAttribute::kImport)) {
return ParseStatusCode::kMalformedTag;
}
if (map.HasValue(XDefineTagAttribute::kName)) {
auto var_name = types::ParseQuotedStringWithoutSubstitution(
map.GetValue(XDefineTagAttribute::kName))
.MapValue(types::VariableName::Parse);
if (!var_name.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(var_name).error());
}
// If "NAME" is defined, "VALUE" must also be defined
if (!map.HasValue(XDefineTagAttribute::kValue)) {
return ParseStatusCode::kMalformedTag;
}
auto value = types::ParseQuotedStringWithoutSubstitution(
map.GetValue(XDefineTagAttribute::kValue), /*allow_empty*/ true);
if (!value.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag);
}
return XDefineTag::CreateDefinition(std::move(var_name).value(),
std::move(value).value().Str());
}
if (map.HasValue(XDefineTagAttribute::kImport)) {
auto var_name = types::ParseQuotedStringWithoutSubstitution(
map.GetValue(XDefineTagAttribute::kImport))
.MapValue(types::VariableName::Parse);
if (!var_name.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(var_name).error());
}
// "VALUE" doesn't make any sense here, but the spec doesn't explicitly
// forbid it. It may be used in the future to provide a default value for
// undefined imported variables, so we won't error on it.
return XDefineTag::CreateImport(std::move(var_name).value());
}
// Without "NAME" or "IMPORT", the tag is malformed
return ParseStatusCode::kMalformedTag;
}
// static
ParseStatus::Or<XIndependentSegmentsTag> XIndependentSegmentsTag::Parse(
TagItem tag) {
return ParseEmptyTag<XIndependentSegmentsTag>(tag);
}
// static
ParseStatus::Or<XVersionTag> XVersionTag::Parse(TagItem tag) {
auto result = ParseDecimalIntegerTag(tag, &XVersionTag::version);
if (!result.has_value()) {
return std::move(result).error();
}
// Reject invalid version numbers.
// For valid version numbers, caller will decide if the version is supported.
auto out = std::move(result).value();
if (out.version == 0) {
return ParseStatusCode::kInvalidPlaylistVersion;
}
return out;
}
struct XMediaTag::CtorArgs {
decltype(XMediaTag::type) type;
decltype(XMediaTag::uri) uri;
decltype(XMediaTag::instream_id) instream_id;
decltype(XMediaTag::group_id) group_id;
decltype(XMediaTag::language) language;
decltype(XMediaTag::associated_language) associated_language;
decltype(XMediaTag::name) name;
decltype(XMediaTag::stable_rendition_id) stable_rendition_id;
decltype(XMediaTag::is_default) is_default;
decltype(XMediaTag::autoselect) autoselect;
decltype(XMediaTag::forced) forced;
decltype(XMediaTag::characteristics) characteristics;
decltype(XMediaTag::channels) channels;
};
XMediaTag::XMediaTag(CtorArgs args)
: type(std::move(args.type)),
uri(std::move(args.uri)),
instream_id(std::move(args.instream_id)),
group_id(std::move(args.group_id)),
language(std::move(args.language)),
associated_language(std::move(args.associated_language)),
name(std::move(args.name)),
stable_rendition_id(std::move(args.stable_rendition_id)),
is_default(std::move(args.is_default)),
autoselect(std::move(args.autoselect)),
forced(std::move(args.forced)),
characteristics(std::move(args.characteristics)),
channels(std::move(args.channels)) {}
XMediaTag::~XMediaTag() = default;
XMediaTag::XMediaTag(const XMediaTag&) = default;
XMediaTag::XMediaTag(XMediaTag&&) = default;
XMediaTag& XMediaTag::operator=(const XMediaTag&) = default;
XMediaTag& XMediaTag::operator=(XMediaTag&&) = default;
// static
ParseStatus::Or<XMediaTag> XMediaTag::Parse(
TagItem tag,
const VariableDictionary& variable_dict,
VariableDictionary::SubstitutionBuffer& sub_buffer) {
DCHECK(tag.GetName() == ToTagName(XMediaTag::kName));
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
// Parse the attribute-list
TypedAttributeMap<XMediaTagAttribute> map;
types::AttributeListIterator iter(*tag.GetContent());
auto map_result = map.FillUntilError(&iter);
if (map_result.code() != ParseStatusCode::kReachedEOF) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(map_result));
}
// Parse the 'TYPE' attribute
MediaType type;
if (map.HasValue(XMediaTagAttribute::kType)) {
auto str = map.GetValue(XMediaTagAttribute::kType);
if (str.Str() == "AUDIO") {
type = MediaType::kAudio;
} else if (str.Str() == "VIDEO") {
type = MediaType::kVideo;
} else if (str.Str() == "SUBTITLES") {
type = MediaType::kSubtitles;
} else if (str.Str() == "CLOSED-CAPTIONS") {
type = MediaType::kClosedCaptions;
} else {
return ParseStatusCode::kMalformedTag;
}
} else {
return ParseStatusCode::kMalformedTag;
}
// Parse the 'URI' attribute
std::optional<ResolvedSourceString> uri;
if (map.HasValue(XMediaTagAttribute::kUri)) {
// This attribute MUST NOT be defined for closed-captions renditions
if (type == MediaType::kClosedCaptions) {
return ParseStatusCode::kMalformedTag;
}
auto result = types::ParseQuotedString(
map.GetValue(XMediaTagAttribute::kUri), variable_dict, sub_buffer);
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
uri = std::move(result).value();
} else {
// URI MUST be defined for subtitle renditions
if (type == MediaType::kSubtitles) {
return ParseStatusCode::kMalformedTag;
}
}
// Parse the 'GROUP-ID' attribute
std::optional<ResolvedSourceString> group_id;
if (map.HasValue(XMediaTagAttribute::kGroupId)) {
auto result = types::ParseQuotedString(
map.GetValue(XMediaTagAttribute::kGroupId), variable_dict, sub_buffer);
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
group_id = std::move(result).value();
} else {
return ParseStatusCode::kMalformedTag;
}
// Parse the 'LANGUAGE' attribute
std::optional<ResolvedSourceString> language;
if (map.HasValue(XMediaTagAttribute::kLanguage)) {
auto result = types::ParseQuotedString(
map.GetValue(XMediaTagAttribute::kLanguage), variable_dict, sub_buffer);
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
language = std::move(result).value();
}
// Parse the 'ASSOC-LANGUAGE' attribute
std::optional<ResolvedSourceString> assoc_language;
if (map.HasValue(XMediaTagAttribute::kAssocLanguage)) {
auto result = types::ParseQuotedString(
map.GetValue(XMediaTagAttribute::kAssocLanguage), variable_dict,
sub_buffer);
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
assoc_language = std::move(result).value();
}
// Parse the 'NAME' attribute
std::optional<ResolvedSourceString> name;
if (map.HasValue(XMediaTagAttribute::kName)) {
auto result = types::ParseQuotedString(
map.GetValue(XMediaTagAttribute::kName), variable_dict, sub_buffer);
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
name = std::move(result).value();
} else {
return ParseStatusCode::kMalformedTag;
}
// Parse the 'STABLE-RENDITION-ID' attribute
std::optional<types::StableId> stable_rendition_id;
if (map.HasValue(XMediaTagAttribute::kStableRenditionId)) {
auto result = types::ParseQuotedString(
map.GetValue(XMediaTagAttribute::kStableRenditionId),
variable_dict, sub_buffer)
.MapValue(types::StableId::Parse);
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
stable_rendition_id = std::move(result).value();
}
// Parse the 'DEFAULT' attribute
bool is_default = false;
if (map.HasValue(XMediaTagAttribute::kDefault)) {
if (map.GetValue(XMediaTagAttribute::kDefault).Str() == "YES") {
is_default = true;
}
}
// Parse the 'AUTOSELECT' attribute
bool autoselect = is_default;
if (map.HasValue(XMediaTagAttribute::kAutoselect)) {
if (map.GetValue(XMediaTagAttribute::kAutoselect).Str() == "YES") {
autoselect = true;
} else if (is_default) {
// If the 'DEFAULT' attribute is 'YES', then the value of this attribute
// must also be 'YES', if present.
return ParseStatusCode::kMalformedTag;
}
}
// Parse the 'FORCED' attribute
bool forced = false;
if (map.HasValue(XMediaTagAttribute::kForced)) {
// The FORCED attribute MUST NOT be present unless TYPE=SUBTITLES
if (type != MediaType::kSubtitles) {
return ParseStatusCode::kMalformedTag;
}
if (map.GetValue(XMediaTagAttribute::kForced).Str() == "YES") {
forced = true;
}
}
// Parse the 'INSTREAM-ID' attribute
std::optional<types::InstreamId> instream_id;
if (map.HasValue(XMediaTagAttribute::kInstreamId)) {
// The INSTREAM-ID attribute MUST NOT be present unless TYPE=CLOSED-CAPTIONS
if (type != MediaType::kClosedCaptions) {
return ParseStatusCode::kMalformedTag;
}
auto result =
types::ParseQuotedString(map.GetValue(XMediaTagAttribute::kInstreamId),
variable_dict, sub_buffer)
.MapValue(types::InstreamId::Parse);
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
instream_id = std::move(result).value();
}
// This attribute is REQUIRED if TYPE=CLOSED-CAPTIONS
else if (type == MediaType::kClosedCaptions) {
return ParseStatusCode::kMalformedTag;
}
// Parse the 'CHARACTERISTICS' attribute
std::vector<std::string> characteristics;
if (map.HasValue(XMediaTagAttribute::kCharacteristics)) {
auto result = types::ParseQuotedString(
map.GetValue(XMediaTagAttribute::kCharacteristics), variable_dict,
sub_buffer);
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
auto value = std::move(result).value();
while (!value.Empty()) {
const auto mct = value.ConsumeDelimiter(',');
if (mct.Empty()) {
return ParseStatusCode::kMalformedTag;
}
characteristics.emplace_back(mct.Str());
}
}
// Parse the 'CHANNELS' attribute
std::optional<types::AudioChannels> channels;
if (map.HasValue(XMediaTagAttribute::kChannels)) {
// Currently only supported type with channel information is `kAudio`.
if (type == MediaType::kAudio) {
auto result =
types::ParseQuotedString(map.GetValue(XMediaTagAttribute::kChannels),
variable_dict, sub_buffer)
.MapValue(types::AudioChannels::Parse);
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
channels = std::move(result).value();
}
}
return XMediaTag(XMediaTag::CtorArgs{
.type = type,
.uri = uri,
.instream_id = instream_id,
.group_id = group_id.value(),
.language = language,
.associated_language = assoc_language,
.name = name.value(),
.stable_rendition_id = std::move(stable_rendition_id),
.is_default = is_default,
.autoselect = autoselect,
.forced = forced,
.characteristics = std::move(characteristics),
.channels = std::move(channels),
});
}
XStreamInfTag::XStreamInfTag() = default;
XStreamInfTag::~XStreamInfTag() = default;
XStreamInfTag::XStreamInfTag(const XStreamInfTag&) = default;
XStreamInfTag::XStreamInfTag(XStreamInfTag&&) = default;
XStreamInfTag& XStreamInfTag::operator=(const XStreamInfTag&) = default;
XStreamInfTag& XStreamInfTag::operator=(XStreamInfTag&&) = default;
// static
ParseStatus::Or<XStreamInfTag> XStreamInfTag::Parse(
TagItem tag,
const VariableDictionary& variable_dict,
VariableDictionary::SubstitutionBuffer& sub_buffer) {
DCHECK(tag.GetName() == ToTagName(XStreamInfTag::kName));
XStreamInfTag out;
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
// Parse the attribute-list
TypedAttributeMap<XStreamInfTagAttribute> map;
types::AttributeListIterator iter(*tag.GetContent());
auto map_result = map.FillUntilError(&iter);
if (map_result.code() != ParseStatusCode::kReachedEOF) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(map_result));
}
// Extract the 'BANDWIDTH' attribute
if (map.HasValue(XStreamInfTagAttribute::kBandwidth)) {
auto bandwidth = types::ParseDecimalInteger(
map.GetValue(XStreamInfTagAttribute::kBandwidth)
.SkipVariableSubstitution());
if (!bandwidth.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(bandwidth).error());
}
out.bandwidth = std::move(bandwidth).value();
} else {
return ParseStatusCode::kMalformedTag;
}
// Extract the 'AVERAGE-BANDWIDTH' attribute
if (map.HasValue(XStreamInfTagAttribute::kAverageBandwidth)) {
auto average_bandwidth = types::ParseDecimalInteger(
map.GetValue(XStreamInfTagAttribute::kAverageBandwidth)
.SkipVariableSubstitution());
if (!average_bandwidth.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(average_bandwidth).error());
}
out.average_bandwidth = std::move(average_bandwidth).value();
}
// Extract the 'SCORE' attribute
if (map.HasValue(XStreamInfTagAttribute::kScore)) {
auto score = types::ParseDecimalFloatingPoint(
map.GetValue(XStreamInfTagAttribute::kScore)
.SkipVariableSubstitution());
if (!score.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(score).error());
}
out.score = std::move(score).value();
}
// Extract the 'CODECS' attribute
if (map.HasValue(XStreamInfTagAttribute::kCodecs)) {
auto codecs_string =
types::ParseQuotedString(map.GetValue(XStreamInfTagAttribute::kCodecs),
variable_dict, sub_buffer);
if (!codecs_string.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(codecs_string).error());
}
// Split the list of codecs
std::vector<std::string> codecs;
SplitCodecs(std::move(codecs_string).value().Str(), &codecs);
out.codecs = std::move(codecs);
}
// Extract the 'RESOLUTION' attribute
if (map.HasValue(XStreamInfTagAttribute::kResolution)) {
auto resolution = types::DecimalResolution::Parse(
map.GetValue(XStreamInfTagAttribute::kResolution)
.SkipVariableSubstitution());
if (!resolution.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(resolution).error());
}
out.resolution = std::move(resolution).value();
}
// Extract the 'FRAME-RATE' attribute
if (map.HasValue(XStreamInfTagAttribute::kFrameRate)) {
auto frame_rate = types::ParseDecimalFloatingPoint(
map.GetValue(XStreamInfTagAttribute::kFrameRate)
.SkipVariableSubstitution());
if (!frame_rate.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(frame_rate).error());
}
out.frame_rate = std::move(frame_rate).value();
}
// Extract the 'AUDIO' attribute
if (map.HasValue(XStreamInfTagAttribute::kAudio)) {
auto audio =
types::ParseQuotedString(map.GetValue(XStreamInfTagAttribute::kAudio),
variable_dict, sub_buffer);
if (!audio.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(audio).error());
}
out.audio = std::move(audio).value();
}
// Extract the 'VIDEO' attribute
if (map.HasValue(XStreamInfTagAttribute::kVideo)) {
auto video =
types::ParseQuotedString(map.GetValue(XStreamInfTagAttribute::kVideo),
variable_dict, sub_buffer);
if (!video.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(video).error());
}
out.video = std::move(video).value();
}
return out;
}
// static
ParseStatus::Or<InfTag> InfTag::Parse(TagItem tag) {
DCHECK(tag.GetName() == ToTagName(InfTag::kName));
if (!tag.GetContent()) {
return ParseStatusCode::kMalformedTag;
}
auto content = *tag.GetContent();
// Inf tags have the form #EXTINF:<duration>,[<title>]
// Find the comma.
auto comma = content.Str().find_first_of(',');
SourceString duration_str = content;
SourceString title_str = content;
if (comma == std::string_view::npos) {
// While the HLS spec does require commas at the end of inf tags, it's
// incredibly common for sites to elide the comma if there is no title
// attribute present. In this case, we should assert that there is at least
// a trailing newline, and then strip it to generate a nameless tag.
title_str = content.Substr(0, 0);
if (*content.Str().end() != '\n') {
duration_str = content;
} else {
duration_str = content.Substr(0, content.Str().length() - 1);
}
} else {
duration_str = content.Substr(0, comma);
title_str = content.Substr(comma + 1);
}
// Extract duration
// TODO(crbug.com/40210233): Below version 3 this should be rounded to an
// integer
auto duration_result =
types::ParseDecimalFloatingPoint(duration_str.SkipVariableSubstitution());
if (!duration_result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(duration_result).error());
}
const auto duration = base::Seconds(std::move(duration_result).value());
if (duration.is_max()) {
return ParseStatusCode::kValueOverflowsTimeDelta;
}
return InfTag{.duration = duration, .title = title_str};
}
// static
ParseStatus::Or<XBitrateTag> XBitrateTag::Parse(TagItem tag) {
return ParseDecimalIntegerTag(tag, &XBitrateTag::bitrate);
}
// static
ParseStatus::Or<XByteRangeTag> XByteRangeTag::Parse(TagItem tag) {
DCHECK(tag.GetName() == ToTagName(XByteRangeTag::kName));
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
auto range = types::parsing::ByteRangeExpression::Parse(
tag.GetContent()->SkipVariableSubstitution());
if (!range.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(range).error());
}
return XByteRangeTag{.range = std::move(range).value()};
}
// static
ParseStatus::Or<XDiscontinuityTag> XDiscontinuityTag::Parse(TagItem tag) {
return ParseEmptyTag<XDiscontinuityTag>(tag);
}
// static
ParseStatus::Or<XDiscontinuitySequenceTag> XDiscontinuitySequenceTag::Parse(
TagItem tag) {
return ParseDecimalIntegerTag(tag, &XDiscontinuitySequenceTag::number);
}
// static
ParseStatus::Or<XEndListTag> XEndListTag::Parse(TagItem tag) {
return ParseEmptyTag<XEndListTag>(tag);
}
// static
ParseStatus::Or<XGapTag> XGapTag::Parse(TagItem tag) {
return ParseEmptyTag<XGapTag>(tag);
}
// static
ParseStatus::Or<XIFramesOnlyTag> XIFramesOnlyTag::Parse(TagItem tag) {
return ParseEmptyTag<XIFramesOnlyTag>(tag);
}
// static
ParseStatus::Or<XMapTag> XMapTag::Parse(
TagItem tag,
const VariableDictionary& variable_dict,
VariableDictionary::SubstitutionBuffer& sub_buffer) {
DCHECK(tag.GetName() == ToTagName(XMapTag::kName));
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
// Parse the attribute-list
TypedAttributeMap<XMapTagAttribute> map;
types::AttributeListIterator iter(*tag.GetContent());
auto map_result = map.FillUntilError(&iter);
if (map_result.code() != ParseStatusCode::kReachedEOF) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(map_result));
}
std::optional<ResolvedSourceString> uri;
if (map.HasValue(XMapTagAttribute::kUri)) {
auto result = types::ParseQuotedString(map.GetValue(XMapTagAttribute::kUri),
variable_dict, sub_buffer);
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
uri = std::move(result).value();
} else {
return ParseStatusCode::kMalformedTag;
}
std::optional<types::parsing::ByteRangeExpression> byte_range;
if (map.HasValue(XMapTagAttribute::kByteRange)) {
auto result =
types::ParseQuotedString(map.GetValue(XMapTagAttribute::kByteRange),
variable_dict, sub_buffer)
.MapValue(types::parsing::ByteRangeExpression::Parse);
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
byte_range = std::move(result).value();
}
return XMapTag{.uri = uri.value(), .byte_range = byte_range};
}
// static
ParseStatus::Or<XMediaSequenceTag> XMediaSequenceTag::Parse(TagItem tag) {
return ParseDecimalIntegerTag(tag, &XMediaSequenceTag::number);
}
// static
ParseStatus::Or<XPartTag> XPartTag::Parse(
TagItem tag,
const VariableDictionary& vars,
VariableDictionary::SubstitutionBuffer& subs) {
DCHECK(tag.GetName() == ToTagName(XPartTag::kName));
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
TypedAttributeMap<XPartTagAttribute> map;
types::AttributeListIterator iter(*tag.GetContent());
auto map_result = map.FillUntilError(&iter);
if (map_result.code() != ParseStatusCode::kReachedEOF) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(map_result));
}
auto uri = ParseField<ResolvedSourceString>(
XPartTagAttribute::kUri, map,
&types::parsing::Quoted<types::parsing::RawStr>::ParseWithSubstitution,
vars, subs);
RETURN_IF_ERROR(uri);
auto duration = ParseField<base::TimeDelta>(
XPartTagAttribute::kDuration, map,
&types::parsing::TimeDelta::ParseWithoutSubstitution);
RETURN_IF_ERROR(duration);
auto byte_range =
ParseField<std::optional<types::parsing::ByteRangeExpression>>(
XPartTagAttribute::kByteRange, map,
&types::parsing::Quoted<
types::parsing::ByteRangeExpression>::ParseWithSubstitution,
vars, subs);
RETURN_IF_ERROR(byte_range);
auto independent = ParseField<std::optional<bool>>(
XPartTagAttribute::kIndependent, map,
&types::parsing::YesOrNo::ParseWithoutSubstitution);
RETURN_IF_ERROR(independent);
auto gap = ParseField<std::optional<bool>>(
XPartTagAttribute::kGap, map,
&types::parsing::YesOrNo::ParseWithoutSubstitution);
RETURN_IF_ERROR(gap);
return XPartTag{.uri = std::move(uri).value(),
.duration = std::move(duration).value(),
.byte_range = std::move(byte_range).value(),
.independent = (*independent).value_or(false),
.gap = (*gap).value_or(false)};
}
// static
ParseStatus::Or<XPartInfTag> XPartInfTag::Parse(TagItem tag) {
DCHECK(tag.GetName() == ToTagName(XPartInfTag::kName));
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
// Parse the attribute-list
TypedAttributeMap<XPartInfTagAttribute> map;
types::AttributeListIterator iter(*tag.GetContent());
auto map_result = map.FillUntilError(&iter);
if (map_result.code() != ParseStatusCode::kReachedEOF) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(map_result));
}
// Extract the 'PART-TARGET' attribute
base::TimeDelta part_target;
if (map.HasValue(XPartInfTagAttribute::kPartTarget)) {
auto result = types::ParseDecimalFloatingPoint(
map.GetValue(XPartInfTagAttribute::kPartTarget)
.SkipVariableSubstitution());
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
part_target = base::Seconds(std::move(result).value());
if (part_target.is_max()) {
return ParseStatusCode::kValueOverflowsTimeDelta;
}
} else {
return ParseStatusCode::kMalformedTag;
}
return XPartInfTag{.target_duration = part_target};
}
// static
ParseStatus::Or<XPlaylistTypeTag> XPlaylistTypeTag::Parse(TagItem tag) {
DCHECK(tag.GetName() == ToTagName(XPlaylistTypeTag::kName));
// This tag requires content
if (!tag.GetContent().has_value() || tag.GetContent()->Empty()) {
return ParseStatusCode::kMalformedTag;
}
if (tag.GetContent()->Str() == "EVENT") {
return XPlaylistTypeTag{.type = PlaylistType::kEvent};
}
if (tag.GetContent()->Str() == "VOD") {
return XPlaylistTypeTag{.type = PlaylistType::kVOD};
}
return ParseStatusCode::kUnknownPlaylistType;
}
// static
ParseStatus::Or<XServerControlTag> XServerControlTag::Parse(TagItem tag) {
DCHECK(tag.GetName() == ToTagName(XServerControlTag::kName));
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
// Parse the attribute-list
TypedAttributeMap<XServerControlTagAttribute> map;
types::AttributeListIterator iter(*tag.GetContent());
auto map_result = map.FillUntilError(&iter);
if (map_result.code() != ParseStatusCode::kReachedEOF) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(map_result));
}
// Extract the 'CAN-SKIP-UNTIL' attribute
std::optional<base::TimeDelta> can_skip_until;
if (map.HasValue(XServerControlTagAttribute::kCanSkipUntil)) {
auto result = types::ParseDecimalFloatingPoint(
map.GetValue(XServerControlTagAttribute::kCanSkipUntil)
.SkipVariableSubstitution());
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
can_skip_until = base::Seconds(std::move(result).value());
if (can_skip_until->is_max()) {
return ParseStatusCode::kValueOverflowsTimeDelta;
}
}
// Extract the 'CAN-SKIP-DATERANGES' attribute
bool can_skip_dateranges = false;
if (map.HasValue(XServerControlTagAttribute::kCanSkipDateRanges)) {
if (map.GetValue(XServerControlTagAttribute::kCanSkipDateRanges).Str() ==
"YES") {
// The existence of this attribute requires the 'CAN-SKIP-UNTIL'
// attribute.
if (!can_skip_until.has_value()) {
return ParseStatusCode::kMalformedTag;
}
can_skip_dateranges = true;
}
}
// Extract the 'HOLD-BACK' attribute
std::optional<base::TimeDelta> hold_back;
if (map.HasValue(XServerControlTagAttribute::kHoldBack)) {
auto result = types::ParseDecimalFloatingPoint(
map.GetValue(XServerControlTagAttribute::kHoldBack)
.SkipVariableSubstitution());
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
hold_back = base::Seconds(std::move(result).value());
if (hold_back->is_max()) {
return ParseStatusCode::kValueOverflowsTimeDelta;
}
}
// Extract the 'PART-HOLD-BACK' attribute
std::optional<base::TimeDelta> part_hold_back;
if (map.HasValue(XServerControlTagAttribute::kPartHoldBack)) {
auto result = types::ParseDecimalFloatingPoint(
map.GetValue(XServerControlTagAttribute::kPartHoldBack)
.SkipVariableSubstitution());
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(result).error());
}
part_hold_back = base::Seconds(std::move(result).value());
if (part_hold_back->is_max()) {
return ParseStatusCode::kValueOverflowsTimeDelta;
}
}
// Extract the 'CAN-BLOCK-RELOAD' attribute
bool can_block_reload = false;
if (map.HasValue(XServerControlTagAttribute::kCanBlockReload)) {
if (map.GetValue(XServerControlTagAttribute::kCanBlockReload).Str() ==
"YES") {
can_block_reload = true;
}
}
return XServerControlTag{
.skip_boundary = can_skip_until,
.can_skip_dateranges = can_skip_dateranges,
.hold_back = hold_back,
.part_hold_back = part_hold_back,
.can_block_reload = can_block_reload,
};
}
// static
ParseStatus::Or<XTargetDurationTag> XTargetDurationTag::Parse(TagItem tag) {
DCHECK(tag.GetName() == ToTagName(XTargetDurationTag::kName));
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
auto duration_result = types::ParseDecimalInteger(
tag.GetContent().value().SkipVariableSubstitution());
if (!duration_result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(duration_result).error());
}
auto duration = base::Seconds(std::move(duration_result).value());
if (duration.is_max()) {
return ParseStatusCode::kValueOverflowsTimeDelta;
}
return XTargetDurationTag{.duration = duration};
}
XSkipTag::XSkipTag() = default;
XSkipTag::~XSkipTag() = default;
XSkipTag::XSkipTag(const XSkipTag&) = default;
XSkipTag::XSkipTag(XSkipTag&&) = default;
ParseStatus::Or<XSkipTag> XSkipTag::Parse(
TagItem tag,
const VariableDictionary& variable_dict,
VariableDictionary::SubstitutionBuffer& sub_buffer) {
DCHECK(tag.GetName() == ToTagName(XSkipTag::kName));
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
XSkipTag out;
TypedAttributeMap<XSkipTagAttribute> map;
types::AttributeListIterator iter(*tag.GetContent());
auto map_result = map.FillUntilError(&iter);
if (map_result.code() != ParseStatusCode::kReachedEOF) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(map_result));
}
if (!map.HasValue(XSkipTagAttribute::kSkippedSegments)) {
return ParseStatusCode::kMalformedTag;
}
auto skip_result = types::ParseDecimalInteger(
map.GetValue(XSkipTagAttribute::kSkippedSegments)
.SkipVariableSubstitution());
if (!skip_result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(skip_result).error());
}
out.skipped_segments = std::move(skip_result).value();
if (map.HasValue(XSkipTagAttribute::kRecentlyRemovedDateranges)) {
// TODO(bug/314833987): Should this list have substitution?
auto removed_result = types::ParseQuotedString(
map.GetValue(XSkipTagAttribute::kRecentlyRemovedDateranges),
variable_dict, sub_buffer, /*allow_empty=*/true);
if (!removed_result.has_value()) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(removed_result).error());
}
auto tab_joined_daterange_ids = std::move(removed_result).value();
std::vector<std::string> removed_dateranges = {};
while (!tab_joined_daterange_ids.Empty()) {
const auto daterange_id = tab_joined_daterange_ids.ConsumeDelimiter('\t');
if (daterange_id.Empty()) {
return ParseStatusCode::kMalformedTag;
}
// TODO(bug/314833987): What type should this be parsed into?
removed_dateranges.emplace_back(daterange_id.Str());
}
out.recently_removed_dateranges = std::move(removed_dateranges);
}
return out;
}
ParseStatus::Or<XRenditionReportTag> XRenditionReportTag::Parse(
TagItem tag,
const VariableDictionary& variable_dict,
VariableDictionary::SubstitutionBuffer& sub_buffer) {
CHECK(tag.GetName() == ToTagName(XRenditionReportTag::kName));
if (!tag.GetContent().has_value()) {
return ParseStatusCode::kMalformedTag;
}
XRenditionReportTag out;
TypedAttributeMap<XRenditionReportTagAttribute> map;
types::AttributeListIterator iter(*tag.GetContent());
auto map_result = map.FillUntilError(&iter);
if (map_result.code() != ParseStatusCode::kReachedEOF) {
return std::move(map_result).AddHere();
}
if (map.HasValue(XRenditionReportTagAttribute::kUri)) {
auto uri_result = types::ParseQuotedString(
map.GetValue(XRenditionReportTagAttribute::kUri), variable_dict,
sub_buffer);
if (!uri_result.has_value()) {
return std::move(uri_result).error().AddHere();
}
out.uri = std::move(uri_result).value();
}
if (map.HasValue(XRenditionReportTagAttribute::kLastMSN)) {
auto msn_result = types::ParseDecimalInteger(
map.GetValue(XRenditionReportTagAttribute::kLastMSN)
.SkipVariableSubstitution());
if (!msn_result.has_value()) {
return std::move(msn_result).error().AddHere();
}
out.last_msn = std::move(msn_result).value();
}
if (map.HasValue(XRenditionReportTagAttribute::kLastPart)) {
auto part_result = types::ParseDecimalInteger(
map.GetValue(XRenditionReportTagAttribute::kLastPart)
.SkipVariableSubstitution());
if (!part_result.has_value()) {
return std::move(part_result).error().AddHere();
}
out.last_part = std::move(part_result).value();
}
return out;
}
ParseStatus::Or<XProgramDateTimeTag> XProgramDateTimeTag::Parse(TagItem tag) {
return ParseISO8601DateTimeTag(tag, &XProgramDateTimeTag::time);
}
template <typename Attrs>
ParseStatus::Or<TypedAttributeMap<Attrs>> RequireNonEmptyMap(
std::optional<SourceString> content) {
if (!content.has_value()) {
return ParseStatusCode::kMalformedTag;
}
TypedAttributeMap<Attrs> map;
types::AttributeListIterator iter(*content);
auto map_result = map.FillUntilError(&iter);
if (map_result.code() != ParseStatusCode::kReachedEOF) {
return ParseStatus(ParseStatusCode::kMalformedTag)
.AddCause(std::move(map_result));
}
return map;
}
namespace {
constexpr char const* kMethodNone = "NONE";
constexpr char const* kMethodAES128 = "AES-128";
constexpr char const* kMethodAES256 = "AES-256";
constexpr char const* kMethodSampleAES = "SAMPLE-AES";
constexpr char const* kMethodSampleAESCTR = "SAMPLE-AES-CTR";
constexpr char const* kMethodSampleAESCENC = "SAMPLE-AES-CENC";
constexpr char const* kMethodISO230017 = "ISO-23001-7";
constexpr char const* kFmtClearkey = "org.w3.clearkey";
constexpr char const* kFmtWidevine =
"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
constexpr char const* kFmtIdentity = "identity";
ParseStatus::Or<XKeyTagMethod> RecognizeMethod(SourceString content) {
if (content.Str() == kMethodNone) {
return XKeyTagMethod::kNone;
}
if (content.Str() == kMethodAES128) {
return XKeyTagMethod::kAES128;
} else if (content.Str() == kMethodAES256) {
return XKeyTagMethod::kAES256;
} else if (content.Str() == kMethodSampleAES) {
return XKeyTagMethod::kSampleAES;
} else if (content.Str() == kMethodSampleAESCTR) {
return XKeyTagMethod::kSampleAESCTR;
} else if (content.Str() == kMethodSampleAESCENC) {
return XKeyTagMethod::kSampleAESCENC;
} else if (content.Str() == kMethodISO230017) {
return XKeyTagMethod::kISO230017;
} else {
return ParseStatusCode::kMalformedTag;
}
}
ParseStatus::Or<XKeyTagKeyFormat> RecognizeFormat(
std::optional<ResolvedSourceString> content) {
if (!content.has_value()) {
return XKeyTagKeyFormat::kIdentity;
} else if (content->Str() == kFmtClearkey) {
return XKeyTagKeyFormat::kClearKey;
} else if (content->Str() == kFmtWidevine) {
return XKeyTagKeyFormat::kWidevine;
} else if (content->Str() == kFmtIdentity) {
return XKeyTagKeyFormat::kIdentity;
} else {
return XKeyTagKeyFormat::kUnsupported;
}
}
template <typename T>
ParseStatus::Or<T> ValidateKeyTag(
XKeyTagMethod method,
ResolvedSourceString uri,
std::optional<XKeyTag::IVHex::Container> iv,
XKeyTagKeyFormat keyformat,
std::optional<ResolvedSourceString> keyformat_versions) {
// Check for incompatible fields. Some methods _must not_ have an IV
// present, while some formats only have a specific set of allowable
// methods.
if (iv.has_value()) {
switch (method) {
case XKeyTagMethod::kSampleAESCTR:
case XKeyTagMethod::kSampleAESCENC:
case XKeyTagMethod::kISO230017:
return ParseStatusCode::kMalformedTag;
default:
break;
}
}
switch (keyformat) {
case XKeyTagKeyFormat::kIdentity:
case XKeyTagKeyFormat::kUnsupported:
break; // Identity and others always are ok.
case XKeyTagKeyFormat::kClearKey:
case XKeyTagKeyFormat::kWidevine:
switch (method) {
case XKeyTagMethod::kSampleAES:
case XKeyTagMethod::kSampleAESCTR:
case XKeyTagMethod::kSampleAESCENC:
case XKeyTagMethod::kISO230017:
break; // Acceptable methods for widevine and clearkey
case XKeyTagMethod::kNone:
case XKeyTagMethod::kAES128:
case XKeyTagMethod::kAES256:
return ParseStatusCode::kMalformedTag;
}
break;
}
return T{.method = method,
.uri = std::move(uri),
.iv = std::move(iv),
.keyformat = keyformat,
.keyformat_versions = std::move(keyformat_versions)};
}
template <typename T>
ParseStatus::Or<T> ParseKeyTag(TagItem tag,
const VariableDictionary& vars,
VariableDictionary::SubstitutionBuffer& subs) {
return RequireNonEmptyMap<XKeyTagAttribute>(tag.GetContent())
.MapValue([&](auto map) -> ParseStatus::Or<T> {
auto enc_method = ParseField<XKeyTagMethod>(XKeyTagAttribute::kMethod,
map, &RecognizeMethod);
RETURN_IF_ERROR(enc_method);
if constexpr (T::kAllowEmptyMethod) {
if (*enc_method == XKeyTagMethod::kNone) {
if (map.Size() != 1) {
return ParseStatusCode::kMalformedTag;
}
return T{.method = *enc_method};
}
}
auto enc_uri = ParseField<ResolvedSourceString>(
XKeyTagAttribute::kUri, map,
&types::parsing::Quoted<
types::parsing::RawStr>::ParseWithSubstitution,
vars, subs);
RETURN_IF_ERROR(enc_uri);
auto enc_iv = ParseField<std::optional<XKeyTag::IVHex::Container>>(
XKeyTagAttribute::kIv, map,
&XKeyTag::IVHex::ParseWithoutSubstitution);
RETURN_IF_ERROR(enc_iv);
auto enc_keyformat =
ParseField<std::optional<ResolvedSourceString>>(
XKeyTagAttribute::kKeyFormat, map,
&types::parsing::Quoted<
types::parsing::RawStr>::ParseWithoutSubstitution)
.MapValue(&RecognizeFormat);
RETURN_IF_ERROR(enc_keyformat);
auto enc_keyformat_versions =
ParseField<std::optional<ResolvedSourceString>>(
XKeyTagAttribute::kKeyFormatVersions, map,
&types::parsing::RawStr::ParseWithoutSubstitution);
RETURN_IF_ERROR(enc_keyformat_versions);
return ValidateKeyTag<T>(*enc_method, *enc_uri, *enc_iv, *enc_keyformat,
*enc_keyformat_versions);
});
}
} // namespace
ParseStatus::Or<XKeyTag> XKeyTag::Parse(
TagItem tag,
const VariableDictionary& vars,
VariableDictionary::SubstitutionBuffer& subs) {
DCHECK(tag.GetName() == ToTagName(XKeyTag::kName));
return ParseKeyTag<XKeyTag>(tag, vars, subs);
}
ParseStatus::Or<XSessionKeyTag> XSessionKeyTag::Parse(
TagItem tag,
const VariableDictionary& vars,
VariableDictionary::SubstitutionBuffer& subs) {
DCHECK(tag.GetName() == ToTagName(XSessionKeyTag::kName));
return ParseKeyTag<XSessionKeyTag>(tag, vars, subs);
}
#undef RETURN_IF_ERROR
} // namespace media::hls