pub mod link_provenance;
mod parser;
mod sanitize;
pub mod sanitize_by_tier;
mod ssrf_guard;
pub mod types;
mod validate;
mod validate_config;
pub use parser::ExpressionParser;
pub use sanitize::{
sanitize_url, sanitize_url_strict, sanitize_url_with_schemes, DEFAULT_SCHEMES, STRICT_SCHEMES,
};
pub use ssrf_guard::is_private_host;
pub use types::{Config, Link, LinkWithId, Macro, Protocol, ProtocolHandler, RegexValidation, Tier};
pub use validate::validate_regex;
pub use validate_config::{
sanitize_link_urls, validate_config, validate_config_with_options, ValidateOptions,
};
use sanitize::sanitize_link;
use std::collections::HashMap;
#[must_use]
pub fn resolve(config: &Config, expression: &str) -> Vec<LinkWithId> {
let mut parser = ExpressionParser::new(config);
let ids = parser.query(expression, "");
ids.iter()
.filter_map(|id| {
config.all_links.get(id).map(|link| LinkWithId {
id: id.clone(),
link: sanitize_link(link),
})
})
.collect()
}
#[must_use]
pub fn cherry_pick(config: &Config, expression: &str) -> HashMap<String, Link> {
let mut parser = ExpressionParser::new(config);
let ids = parser.query(expression, "");
ids.iter()
.filter_map(|id| {
config
.all_links
.get(id)
.map(|link| (id.clone(), sanitize_link(link)))
})
.collect()
}
#[must_use]
pub fn merge_configs(configs: &[&Config]) -> Config {
const BLOCKED: &[&str] = &["__proto__", "constructor", "prototype"];
let mut merged = Config::default();
for cfg in configs {
for (k, v) in &cfg.settings {
if !BLOCKED.contains(&k.as_str()) {
merged.settings.insert(k.clone(), v.clone());
}
}
for (k, v) in &cfg.macros {
if !BLOCKED.contains(&k.as_str()) {
merged.macros.insert(k.clone(), v.clone());
}
}
for (k, v) in &cfg.all_links {
if !BLOCKED.contains(&k.as_str()) {
merged.all_links.insert(k.clone(), v.clone());
}
}
for (k, v) in &cfg.search_patterns {
if !BLOCKED.contains(&k.as_str()) {
merged.search_patterns.insert(k.clone(), v.clone());
}
}
}
merged
}