resource policy trusted destinations (#875)

* **New Features**
  * Added a Resource Policy editor for each input, supporting trusted hostnames and private network ranges.
  * Resource URLs now retain their source input and use authenticated links where supported.
  * Resource caching is scoped to the policy authorizing access.

* **Bug Fixes**
  * Invalid or unsupported resource values are safely discarded.
  * Redirects and destination addresses are rechecked against the applicable policy.

* **Breaking Changes**
  * Private DNS destinations require approved hosts and networks; private IP literals require an approved network.
  * Input and alias names must be non-empty and globally unique.
This commit is contained in:
euzu
2026-09-22 15:02:22 +02:00
committed by GitHub
parent fbc1e669fe
commit 2adc18adbc
55 changed files with 3575 additions and 309 deletions
@@ -1563,6 +1563,7 @@ mod mapping_stage {
match_names: vec!["BBC One".intern()],
config: Box::new(IcsEpgSourceConfig::default()),
},
input_name: None,
}]);
let mut playlist = FetchedPlaylist {
@@ -1658,6 +1659,7 @@ match {
match_names: vec![],
config: Box::new(IcsEpgSourceConfig::default()),
},
input_name: None,
}]);
let mut playlist = FetchedPlaylist {
input: &input,
@@ -164,6 +164,12 @@ impl MappingStageOutcome {
}
pub(crate) fn map_channel(mut channel: PlaylistItem, mapping: &CompiledMapping) -> ChannelMappingOutcome {
let mut trusted_locators = HashSet::new();
channel.header.visit_resource_values(&mut |value| {
if shared::model::resolve_resource_value(value).is_ok_and(|locator| locator.is_some()) {
trusted_locators.insert(Arc::clone(value));
}
});
let mut matched_rules = 0;
let mut virtual_items = vec![];
let mut changed_fields = HashSet::new();
@@ -198,6 +204,16 @@ pub(crate) fn map_channel(mut channel: PlaylistItem, mapping: &CompiledMapping)
}
}
}
let normalize_mapped_item = |item: &mut PlaylistItem| {
item.header.visit_resource_values_mut(&mut |value| {
if shared::model::has_resource_scheme(value) && !trusted_locators.contains(value) {
*value = Arc::from("");
}
});
item.header.normalize_internal_resource_values();
};
normalize_mapped_item(&mut channel);
virtual_items.iter_mut().for_each(normalize_mapped_item);
ChannelMappingOutcome { channel, virtual_items, matched_rules, changed_fields, diagnostics }
}