1use stix_model::{ObjectStore, ObjectView, StixValue};
4use stix_pattern::ast::{ObjectPath, PathStep};
5
6pub fn resolve_path(
14 obj: &dyn ObjectView,
15 path: &ObjectPath,
16 store: Option<&ObjectStore>,
17) -> Vec<StixValue> {
18 if obj.type_() != Some(path.object_type.as_str()) {
19 return Vec::new();
20 }
21 let mut steps = path.steps.iter();
22
23 let first = match steps.next() {
25 Some(PathStep::Key(k)) => k,
26 _ => return Vec::new(),
28 };
29 let mut current: Vec<StixValue> = match obj.property(first) {
30 Some(v) => vec![v],
31 None => Vec::new(),
32 };
33
34 for step in steps {
35 let mut next = Vec::new();
36 for value in current.drain(..) {
37 apply_step(value, step, store, &mut next);
38 }
39 current = next;
40 }
41 current
42}
43
44fn apply_step(
46 value: StixValue,
47 step: &PathStep,
48 store: Option<&ObjectStore>,
49 out: &mut Vec<StixValue>,
50) {
51 match step {
52 PathStep::Key(key) => match value {
53 StixValue::Object(map) => {
55 if let Some(v) = map.get(key) {
56 out.push(v.clone());
57 }
58 }
59 StixValue::String(id) => {
61 if let Some(store) = store {
62 if let Some(referenced) = store.get(&id) {
63 if let Some(v) = referenced.property(key) {
64 out.push(v);
65 }
66 }
67 }
68 }
69 _ => {}
70 },
71 PathStep::Index(i) => {
72 if let StixValue::List(items) = value {
73 if let Some(v) = items.into_iter().nth(*i as usize) {
74 out.push(v);
75 }
76 }
77 }
78 PathStep::AnyIndex => {
79 if let StixValue::List(items) = value {
80 out.extend(items);
81 }
82 }
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use stix_model::{Bundle, ObjectStore, StixObject, StixValue};
90 use stix_pattern::ast::{ObjectPath, PathStep};
91
92 fn obj(json: serde_json::Value) -> StixObject {
93 StixObject::from_json(json).unwrap()
94 }
95
96 fn path(object_type: &str, steps: Vec<PathStep>) -> ObjectPath {
97 ObjectPath {
98 object_type: object_type.to_string(),
99 steps,
100 }
101 }
102
103 #[test]
104 fn resolves_top_level_property() {
105 let o =
106 obj(serde_json::json!({"type": "ipv4-addr", "id": "ipv4-addr--1", "value": "1.2.3.4"}));
107 let p = path("ipv4-addr", vec![PathStep::Key("value".into())]);
108 assert_eq!(
109 resolve_path(&o, &p, None),
110 vec![StixValue::String("1.2.3.4".into())]
111 );
112 }
113
114 #[test]
115 fn type_mismatch_yields_nothing() {
116 let o =
117 obj(serde_json::json!({"type": "domain-name", "id": "domain-name--1", "value": "x"}));
118 let p = path("ipv4-addr", vec![PathStep::Key("value".into())]);
119 assert!(resolve_path(&o, &p, None).is_empty());
120 }
121
122 #[test]
123 fn resolves_nested_key() {
124 let o = obj(serde_json::json!({
125 "type": "file", "id": "file--1",
126 "hashes": {"SHA-256": "abc"}
127 }));
128 let p = path(
129 "file",
130 vec![
131 PathStep::Key("hashes".into()),
132 PathStep::Key("SHA-256".into()),
133 ],
134 );
135 assert_eq!(
136 resolve_path(&o, &p, None),
137 vec![StixValue::String("abc".into())]
138 );
139 }
140
141 #[test]
142 fn resolves_index_and_any_index() {
143 let o = obj(serde_json::json!({
144 "type": "network-traffic", "id": "network-traffic--1",
145 "protocols": ["ipv4", "tcp"]
146 }));
147 let idx = path(
148 "network-traffic",
149 vec![PathStep::Key("protocols".into()), PathStep::Index(1)],
150 );
151 assert_eq!(
152 resolve_path(&o, &idx, None),
153 vec![StixValue::String("tcp".into())]
154 );
155
156 let any = path(
157 "network-traffic",
158 vec![PathStep::Key("protocols".into()), PathStep::AnyIndex],
159 );
160 assert_eq!(
161 resolve_path(&o, &any, None),
162 vec![
163 StixValue::String("ipv4".into()),
164 StixValue::String("tcp".into())
165 ]
166 );
167 }
168
169 #[test]
170 fn dereferences_ref_through_store() {
171 let bundle = Bundle::from_json_str(
172 r#"{"type":"bundle","id":"bundle--1","objects":[
173 {"type":"ipv4-addr","id":"ipv4-addr--1","value":"1.2.3.4"},
174 {"type":"network-traffic","id":"network-traffic--1","src_ref":"ipv4-addr--1"}
175 ]}"#,
176 )
177 .unwrap();
178 let store = ObjectStore::from_bundle(&bundle);
179 let nt = obj(serde_json::json!({
180 "type": "network-traffic", "id": "network-traffic--1", "src_ref": "ipv4-addr--1"
181 }));
182 let p = path(
183 "network-traffic",
184 vec![
185 PathStep::Key("src_ref".into()),
186 PathStep::Key("value".into()),
187 ],
188 );
189 assert_eq!(
190 resolve_path(&nt, &p, Some(&store)),
191 vec![StixValue::String("1.2.3.4".into())]
192 );
193 }
194
195 #[test]
196 fn missing_property_yields_nothing() {
197 let o = obj(serde_json::json!({"type": "file", "id": "file--1", "name": "x"}));
198 let p = path("file", vec![PathStep::Key("size".into())]);
199 assert!(resolve_path(&o, &p, None).is_empty());
200 }
201}