windows_eventlog_native/
event.rs1use std::collections::HashMap;
2
3use chrono::{DateTime, TimeZone, Utc};
4use quick_xml::escape::resolve_xml_entity;
5use quick_xml::events::Event as XmlEvent;
6use quick_xml::{Reader, XmlVersion};
7
8use crate::error::{Error, Result};
9
10#[derive(Debug, Clone)]
16pub struct Event {
17 pub record_id: u64,
18 pub event_id: u32,
19 pub time_created: DateTime<Utc>,
20 pub provider: String,
21 pub channel: String,
22 pub xml: String,
23 pub data: HashMap<String, String>,
26}
27
28pub fn rendered_xml(e: &Event) -> String {
30 e.xml.clone()
31}
32
33pub fn parse_event_xml(xml: &str, default_channel: &str) -> Result<Event> {
38 let mut reader = Reader::from_str(xml);
39
40 let mut record_id: u64 = 0;
41 let mut event_id: u32 = 0;
42 let mut time_created: Option<DateTime<Utc>> = None;
43 let mut provider = String::new();
44 let mut channel = String::from(default_channel);
45 let mut data: HashMap<String, String> = HashMap::new();
46
47 enum State {
50 Idle,
51 InSystemElement(&'static str), InEventData,
53 InDataNamed(String),
54 InDataAnon(usize),
55 }
56
57 fn apply_text(
58 state: &State,
59 txt: &str,
60 event_id: &mut u32,
61 record_id: &mut u64,
62 channel: &mut String,
63 data: &mut HashMap<String, String>,
64 ) {
65 match state {
66 State::InSystemElement("event_id") => {
67 *event_id = txt.trim().parse().unwrap_or(0);
68 }
69 State::InSystemElement("record_id") => {
70 *record_id = txt.trim().parse().unwrap_or(0);
71 }
72 State::InSystemElement("channel") => {
73 *channel = txt.trim().to_string();
74 }
75 State::InDataNamed(name) => {
76 data.entry(name.clone())
77 .and_modify(|value| value.push_str(txt))
78 .or_insert_with(|| txt.to_string());
79 }
80 State::InDataAnon(index) => {
81 let key = format!("Data_{index}");
82 data.entry(key)
83 .and_modify(|value| value.push_str(txt))
84 .or_insert_with(|| txt.to_string());
85 }
86 _ => {}
87 }
88 }
89
90 let mut state = State::Idle;
91 let mut anon_counter = 0usize;
92 let mut in_event_data = false;
93
94 let mut buf = Vec::new();
95 loop {
96 match reader.read_event_into(&mut buf) {
97 Ok(XmlEvent::Start(e)) => {
98 let name = e.name();
99 let local = std::str::from_utf8(name.as_ref()).unwrap_or("");
100 match local {
101 "EventData" => {
102 in_event_data = true;
103 state = State::InEventData;
104 }
105 "Data" if in_event_data => {
106 let mut named: Option<String> = None;
107 for attr in e.attributes().flatten() {
108 if attr.key.as_ref() == b"Name" {
109 if let Ok(v) = attr.normalized_value(XmlVersion::Implicit1_0) {
110 named = Some(v.into_owned());
111 }
112 }
113 }
114 state = match named {
115 Some(n) => State::InDataNamed(n),
116 None => {
117 let idx = anon_counter;
118 anon_counter += 1;
119 State::InDataAnon(idx)
120 }
121 };
122 }
123 "Provider" => {
124 for attr in e.attributes().flatten() {
125 if attr.key.as_ref() == b"Name" {
126 if let Ok(v) = attr.normalized_value(XmlVersion::Implicit1_0) {
127 provider = v.into_owned();
128 }
129 }
130 }
131 }
132 "EventID" => state = State::InSystemElement("event_id"),
133 "EventRecordID" => state = State::InSystemElement("record_id"),
134 "Channel" => state = State::InSystemElement("channel"),
135 "TimeCreated" => {
136 for attr in e.attributes().flatten() {
137 if attr.key.as_ref() == b"SystemTime" {
138 if let Ok(v) = attr.normalized_value(XmlVersion::Implicit1_0) {
139 if let Ok(t) = DateTime::parse_from_rfc3339(&v) {
141 time_created = Some(t.with_timezone(&Utc));
142 } else if let Ok(t) = chrono::NaiveDateTime::parse_from_str(
143 v.trim_end_matches('Z'),
144 "%Y-%m-%dT%H:%M:%S%.f",
145 ) {
146 time_created = Some(Utc.from_utc_datetime(&t));
147 }
148 }
149 }
150 }
151 }
152 _ => {}
153 }
154 }
155 Ok(XmlEvent::Empty(e)) => {
156 let name = e.name();
158 let local = std::str::from_utf8(name.as_ref()).unwrap_or("");
159 if local == "Data" && in_event_data {
160 let mut named: Option<String> = None;
161 for attr in e.attributes().flatten() {
162 if attr.key.as_ref() == b"Name" {
163 if let Ok(v) = attr.normalized_value(XmlVersion::Implicit1_0) {
164 named = Some(v.into_owned());
165 }
166 }
167 }
168 match named {
169 Some(n) => {
170 data.insert(n, String::new());
171 }
172 None => {
173 data.insert(format!("Data_{}", anon_counter), String::new());
174 anon_counter += 1;
175 }
176 }
177 } else if local == "TimeCreated" {
178 for attr in e.attributes().flatten() {
179 if attr.key.as_ref() == b"SystemTime" {
180 if let Ok(v) = attr.normalized_value(XmlVersion::Implicit1_0) {
181 if let Ok(t) = DateTime::parse_from_rfc3339(&v) {
182 time_created = Some(t.with_timezone(&Utc));
183 }
184 }
185 }
186 }
187 } else if local == "Provider" {
188 for attr in e.attributes().flatten() {
189 if attr.key.as_ref() == b"Name" {
190 if let Ok(v) = attr.normalized_value(XmlVersion::Implicit1_0) {
191 provider = v.into_owned();
192 }
193 }
194 }
195 }
196 }
197 Ok(XmlEvent::Text(t)) => {
198 let txt = t.xml10_content().unwrap_or_default();
199 apply_text(
200 &state,
201 &txt,
202 &mut event_id,
203 &mut record_id,
204 &mut channel,
205 &mut data,
206 );
207 }
208 Ok(XmlEvent::GeneralRef(reference)) => {
209 let resolved = match reference.resolve_char_ref() {
210 Ok(Some(character)) => character.to_string(),
211 Ok(None) => {
212 let name = std::str::from_utf8(reference.as_ref()).unwrap_or_default();
213 resolve_xml_entity(name).unwrap_or_default().to_string()
214 }
215 Err(_) => String::new(),
216 };
217 apply_text(
218 &state,
219 &resolved,
220 &mut event_id,
221 &mut record_id,
222 &mut channel,
223 &mut data,
224 );
225 }
226 Ok(XmlEvent::End(e)) => {
227 let name = e.name();
228 let local = std::str::from_utf8(name.as_ref()).unwrap_or("");
229 if local == "EventData" {
230 in_event_data = false;
231 state = State::Idle;
232 } else if matches!(local, "EventID" | "EventRecordID" | "Channel" | "Data") {
233 state = if in_event_data {
234 State::InEventData
235 } else {
236 State::Idle
237 };
238 }
239 }
240 Ok(XmlEvent::Eof) => break,
241 Err(e) => return Err(Error::Xml(e.to_string())),
242 _ => {}
243 }
244 buf.clear();
245 }
246
247 Ok(Event {
248 record_id,
249 event_id,
250 time_created: time_created.unwrap_or_else(Utc::now),
251 provider,
252 channel,
253 xml: xml.to_string(),
254 data,
255 })
256}
257
258pub fn filetime_to_utc(ft: u64) -> Result<DateTime<Utc>> {
264 const SEC_1601_TO_1970: i64 = 11_644_473_600;
266 let ticks = ft as i128;
267 let secs = (ticks / 10_000_000) as i64 - SEC_1601_TO_1970;
268 let nsecs = ((ticks % 10_000_000) * 100) as u32;
269 Utc.timestamp_opt(secs, nsecs)
270 .single()
271 .ok_or(Error::BadFileTime(ft))
272}