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