use starlark::environment::{GlobalsBuilder, LibraryExtension};
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::{Value, ValueLike};
use crate::builders::base::BasePolicyValue;
use crate::builders::match_tree::{self as mt, MatchTreeNode, path_value_to_json, pattern_to_json};
pub fn clash_globals() -> starlark::environment::Globals {
let mut builder = GlobalsBuilder::standard();
LibraryExtension::StructType.add(&mut builder);
register_globals(&mut builder);
builder.build()
}
fn caller_source_location(eval: &Evaluator) -> Option<String> {
let stack = eval.call_stack();
for frame in &stack.frames {
if let Some(loc) = &frame.location {
let filename = loc.file.filename();
if !filename.starts_with("@clash//") {
return Some(loc.to_string());
}
}
}
None
}
fn starlark_to_json(value: Value) -> anyhow::Result<serde_json::Value> {
let json_str = value.to_json()?;
serde_json::from_str(&json_str).map_err(Into::into)
}
#[starlark_module]
fn register_globals(builder: &mut GlobalsBuilder) {
const _ALLOW: &str = "allow";
const _DENY: &str = "deny";
const _ASK: &str = "ask";
const _OS: &str = std::env::consts::OS;
const _ARCH: &str = std::env::consts::ARCH;
fn _mt_node<'v>(#[starlark(require = pos)] value: Value<'v>) -> anyhow::Result<MatchTreeNode> {
let json = starlark_to_json(value)?;
Ok(MatchTreeNode { json })
}
fn _mt_condition<'v>(
#[starlark(require = pos)] observe: Value<'v>,
#[starlark(require = pos)] pattern: &MatchTreeNode,
#[starlark(require = named)] doc: Option<&str>,
eval: &mut Evaluator<'v, '_, '_>,
) -> anyhow::Result<MatchTreeNode> {
let observe_json = starlark_to_json(observe)?;
let source = caller_source_location(eval);
Ok(mt::mt_condition_with_doc(
observe_json,
pattern.json.clone(),
doc.map(|s| s.to_string()),
source,
))
}
fn _mt_pattern<'v>(
#[starlark(require = pos)] value: Value<'v>,
heap: &'v starlark::values::Heap,
) -> anyhow::Result<MatchTreeNode> {
let pat = pattern_to_json(value, heap)?;
Ok(MatchTreeNode { json: pat })
}
fn _mt_prefix<'v>(
#[starlark(require = pos)] value: Value<'v>,
heap: &'v starlark::values::Heap,
) -> anyhow::Result<MatchTreeNode> {
let val_json = path_value_to_json(value, heap)?;
Ok(MatchTreeNode {
json: serde_json::json!({"prefix": val_json}),
})
}
fn _mt_literal<'v>(
#[starlark(require = pos)] value: Value<'v>,
heap: &'v starlark::values::Heap,
) -> anyhow::Result<MatchTreeNode> {
let val_json = path_value_to_json(value, heap)?;
Ok(MatchTreeNode {
json: serde_json::json!({"literal": val_json}),
})
}
fn _mt_policy<'v>(
#[starlark(require = named)] default: Option<&str>,
#[starlark(require = named)] sandboxes: Option<Value<'v>>,
#[starlark(require = named)] rules: Option<Value<'v>>,
#[starlark(require = named)] default_sandbox: Option<Value<'v>>,
) -> anyhow::Result<BasePolicyValue> {
use serde_json::json;
let default_effect = default.unwrap_or("deny").to_string();
let mut sandbox_map = serde_json::Map::new();
if let Some(sb_val) = sandboxes
&& let Some(list) = starlark::values::list::ListRef::from_value(sb_val)
{
for item in list.iter() {
let sb_json = starlark_to_json(item)?;
if let Some(name) = sb_json.get("name").and_then(|n| n.as_str()) {
sandbox_map.insert(name.to_string(), sb_json);
}
}
}
let mut tree_nodes = Vec::new();
if let Some(rules_val) = rules
&& let Some(list) = starlark::values::list::ListRef::from_value(rules_val)
{
for item in list.iter() {
if let Some(node) = item.downcast_ref::<MatchTreeNode>() {
tree_nodes.push(node.json.clone());
} else {
anyhow::bail!(
"match tree policy rules must be MatchTreeNode values, got {}",
item.get_type()
);
}
}
}
let default_sandbox_json = match default_sandbox {
Some(val) if !val.is_none() => {
let sb_json = starlark_to_json(val)?;
sb_json
.get("name")
.and_then(|n| n.as_str())
.map(|name| json!(name))
}
_ => None,
};
let mut doc = json!({
"schema_version": 5,
"default_effect": default_effect,
"sandboxes": sandbox_map,
"tree": tree_nodes,
});
if let Some(ds) = default_sandbox_json {
doc.as_object_mut()
.unwrap()
.insert("default_sandbox".to_string(), ds);
}
Ok(BasePolicyValue {
base_doc: Some(doc),
default_effect,
})
}
}