///|
pub(all) struct PublishPolicy {
require_end_list : Bool
allow_absolute_uris : Bool
allow_parent_traversal : Bool
max_target_duration : Int
max_segment_duration_ms : Int
max_variant_bandwidth : Int
min_variants : Int
require_independent_segments : Bool
require_version_at_least : Int
} derive(Eq, @debug.Debug)
///|
pub(all) struct PolicyReport {
ok : Bool
errors : Int
warnings : Int
checks : Int
issues : Array[ValidationIssue]
} derive(Eq, @debug.Debug)
///|
pub fn PublishPolicy::vod_default() -> PublishPolicy {
{
require_end_list: true,
allow_absolute_uris: true,
allow_parent_traversal: false,
max_target_duration: 12,
max_segment_duration_ms: 12000,
max_variant_bandwidth: 0,
min_variants: 0,
require_independent_segments: false,
require_version_at_least: 3,
}
}
///|
pub fn PublishPolicy::live_default() -> PublishPolicy {
{
require_end_list: false,
allow_absolute_uris: true,
allow_parent_traversal: false,
max_target_duration: 10,
max_segment_duration_ms: 10000,
max_variant_bandwidth: 0,
min_variants: 0,
require_independent_segments: true,
require_version_at_least: 3,
}
}
///|
pub fn PublishPolicy::strict_master_default() -> PublishPolicy {
{
require_end_list: false,
allow_absolute_uris: true,
allow_parent_traversal: false,
max_target_duration: 0,
max_segment_duration_ms: 0,
max_variant_bandwidth: 12000000,
min_variants: 2,
require_independent_segments: true,
require_version_at_least: 4,
}
}
///|
pub fn evaluate_publish_policy(
playlist : HlsPlaylist,
policy : PublishPolicy,
) -> PolicyReport {
let issues : Array[ValidationIssue] = []
let mut checks = 0
checks += check_version_policy(playlist, policy, issues)
checks += check_endlist_policy(playlist, policy, issues)
checks += check_independent_policy(playlist, policy, issues)
checks += check_duration_policy(playlist, policy, issues)
checks += check_variant_policy(playlist, policy, issues)
checks += check_uri_policy(playlist, policy, issues)
policy_report_from_issues(checks, issues)
}
///|
pub fn format_policy_report(report : PolicyReport) -> String {
let out = StringBuilder()
out.write_string("policy=ok=")
out.write_string(report.ok.to_string())
out.write_string(", checks=")
out.write_string(report.checks.to_string())
out.write_string(", errors=")
out.write_string(report.errors.to_string())
out.write_string(", warnings=")
out.write_string(report.warnings.to_string())
for issue in report.issues {
out.write_char('\n')
out.write_string(severity_to_string(issue.severity))
out.write_string(" ")
out.write_string(issue.code)
if issue.line > 0 {
out.write_string(" line=")
out.write_string(issue.line.to_string())
}
out.write_string(": ")
out.write_string(issue.message)
}
out.to_string()
}
///|
pub fn policy_report_to_validation(report : PolicyReport) -> ValidationReport {
report_from_issues(report.issues)
}
///|
fn check_version_policy(
playlist : HlsPlaylist,
policy : PublishPolicy,
issues : Array[ValidationIssue],
) -> Int {
if policy.require_version_at_least <= 0 {
return 0
}
if playlist.version < policy.require_version_at_least {
issues.push(
ValidationIssue::warning(
"policy-version-low",
"playlist version is below policy minimum \{policy.require_version_at_least}",
),
)
}
1
}
///|
fn check_endlist_policy(
playlist : HlsPlaylist,
policy : PublishPolicy,
issues : Array[ValidationIssue],
) -> Int {
if policy.require_end_list && !playlist.end_list {
issues.push(
ValidationIssue::error(
"policy-endlist-required", "policy requires EXT-X-ENDLIST",
),
)
}
1
}
///|
fn check_independent_policy(
playlist : HlsPlaylist,
policy : PublishPolicy,
issues : Array[ValidationIssue],
) -> Int {
if policy.require_independent_segments && !playlist.independent_segments {
issues.push(
ValidationIssue::warning(
"policy-independent-segments-required", "policy expects EXT-X-INDEPENDENT-SEGMENTS",
),
)
}
1
}
///|
fn check_duration_policy(
playlist : HlsPlaylist,
policy : PublishPolicy,
issues : Array[ValidationIssue],
) -> Int {
let mut checks = 0
if policy.max_target_duration > 0 {
checks += 1
if playlist.target_duration > policy.max_target_duration {
issues.push(
ValidationIssue::warning(
"policy-target-duration-high",
"target duration exceeds policy maximum \{policy.max_target_duration}",
),
)
}
}
if policy.max_segment_duration_ms > 0 {
checks += 1
for segment in playlist.segments {
if segment.duration_ms > policy.max_segment_duration_ms {
issues.push(
ValidationIssue::warning(
"policy-segment-duration-high",
"segment duration exceeds policy maximum \{millis_to_string(policy.max_segment_duration_ms)}",
line=segment.line,
),
)
}
}
}
checks
}
///|
fn check_variant_policy(
playlist : HlsPlaylist,
policy : PublishPolicy,
issues : Array[ValidationIssue],
) -> Int {
let mut checks = 0
if policy.min_variants > 0 {
checks += 1
if playlist.variants.length() < policy.min_variants {
issues.push(
ValidationIssue::warning(
"policy-variant-count-low",
"variant count is below policy minimum \{policy.min_variants}",
),
)
}
}
if policy.max_variant_bandwidth > 0 {
checks += 1
for variant in playlist.variants {
if variant.bandwidth > policy.max_variant_bandwidth {
issues.push(
ValidationIssue::warning(
"policy-variant-bandwidth-high",
"variant bandwidth exceeds policy maximum \{policy.max_variant_bandwidth}",
line=variant.line,
),
)
}
}
}
checks
}
///|
fn check_uri_policy(
playlist : HlsPlaylist,
policy : PublishPolicy,
issues : Array[ValidationIssue],
) -> Int {
let audit = audit_uris(playlist)
let mut checks = 1
if !policy.allow_absolute_uris {
checks += 1
if audit.absolute_uris > 0 {
issues.push(
ValidationIssue::error(
"policy-absolute-uri-disallowed", "policy disallows absolute URIs",
),
)
}
}
if !policy.allow_parent_traversal {
checks += 1
if audit.parent_traversal_uris > 0 {
issues.push(
ValidationIssue::error(
"policy-parent-traversal-disallowed", "policy disallows URI parent directory traversal",
),
)
}
}
if audit.empty_uris > 0 {
issues.push(
ValidationIssue::error("policy-empty-uri", "policy rejects empty URIs"),
)
}
checks
}
///|
fn policy_report_from_issues(
checks : Int,
issues : Array[ValidationIssue],
) -> PolicyReport {
let mut errors = 0
let mut warnings = 0
for issue in issues {
match issue.severity {
Error => errors += 1
Warning => warnings += 1
Info => ()
}
}
{ ok: errors == 0, errors, warnings, checks, issues }
}