provenant/parsers/
microsoft_update_manifest.rs1use crate::models::{DatasourceId, PackageType};
16use std::path::Path;
17
18use crate::parser_warn as warn;
19use quick_xml::events::Event;
20use quick_xml::reader::Reader;
21
22use crate::models::PackageData;
23use crate::parsers::utils::{MAX_ITERATION_COUNT, read_file_to_string, truncate_field};
24
25use super::PackageParser;
26
27const PACKAGE_TYPE: PackageType = PackageType::WindowsUpdate;
28
29pub struct MicrosoftUpdateManifestParser;
30
31impl PackageParser for MicrosoftUpdateManifestParser {
32 const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
33
34 fn is_match(path: &Path) -> bool {
35 path.extension().is_some_and(|ext| ext == "mum")
36 }
37
38 fn extract_packages(path: &Path) -> Vec<PackageData> {
39 let content = match read_file_to_string(path, None) {
40 Ok(c) => c,
41 Err(e) => {
42 warn!("Failed to read .mum file {:?}: {}", path, e);
43 return vec![PackageData {
44 package_type: Some(PACKAGE_TYPE),
45 datasource_id: Some(DatasourceId::MicrosoftUpdateManifestMum),
46 ..Default::default()
47 }];
48 }
49 };
50
51 vec![parse_mum_xml(&content)]
52 }
53}
54
55pub(crate) fn parse_mum_xml(content: &str) -> PackageData {
56 let mut reader = Reader::from_str(content);
57 reader.config_mut().trim_text(true);
58
59 let mut name = None;
60 let mut version = None;
61 let mut description = None;
62 let mut copyright = None;
63 let mut homepage_url = None;
64
65 let mut buf = Vec::new();
66 let mut iteration_count: usize = 0;
67
68 loop {
69 iteration_count += 1;
70 if iteration_count > MAX_ITERATION_COUNT {
71 warn!(
72 "Exceeded MAX_ITERATION_COUNT ({}) parsing .mum XML, stopping",
73 MAX_ITERATION_COUNT
74 );
75 break;
76 }
77 match reader.read_event_into(&mut buf) {
78 Ok(Event::Empty(e)) => {
79 if e.name().as_ref() == b"assemblyIdentity" {
80 for attr in e.attributes().filter_map(|a| a.ok()) {
81 match attr.key.as_ref() {
82 b"name" => {
83 let raw = attr.value.to_vec();
84 let has_invalid = String::from_utf8(raw.clone()).is_err();
85 let val = String::from_utf8_lossy(&raw).into_owned();
86 if has_invalid {
87 warn!(
88 "Invalid UTF-8 in 'name' attribute, using lossy conversion"
89 );
90 }
91 name = Some(truncate_field(val));
92 }
93 b"version" => {
94 let raw = attr.value.to_vec();
95 let has_invalid = String::from_utf8(raw.clone()).is_err();
96 let val = String::from_utf8_lossy(&raw).into_owned();
97 if has_invalid {
98 warn!(
99 "Invalid UTF-8 in 'version' attribute, using lossy conversion"
100 );
101 }
102 version = Some(truncate_field(val));
103 }
104 _ => {}
105 }
106 }
107 }
108 }
109 Ok(Event::Start(e)) => {
110 if e.name().as_ref() == b"assembly" {
111 for attr in e.attributes().filter_map(|a| a.ok()) {
112 match attr.key.as_ref() {
113 b"description" => {
114 let raw = attr.value.to_vec();
115 let has_invalid = String::from_utf8(raw.clone()).is_err();
116 let val = String::from_utf8_lossy(&raw).into_owned();
117 if has_invalid {
118 warn!(
119 "Invalid UTF-8 in 'description' attribute, using lossy conversion"
120 );
121 }
122 description = Some(truncate_field(val));
123 }
124 b"copyright" => {
125 let raw = attr.value.to_vec();
126 let has_invalid = String::from_utf8(raw.clone()).is_err();
127 let val = String::from_utf8_lossy(&raw).into_owned();
128 if has_invalid {
129 warn!(
130 "Invalid UTF-8 in 'copyright' attribute, using lossy conversion"
131 );
132 }
133 copyright = Some(truncate_field(val));
134 }
135 b"supportInformation" => {
136 let raw = attr.value.to_vec();
137 let has_invalid = String::from_utf8(raw.clone()).is_err();
138 let val = String::from_utf8_lossy(&raw).into_owned();
139 if has_invalid {
140 warn!(
141 "Invalid UTF-8 in 'supportInformation' attribute, using lossy conversion"
142 );
143 }
144 homepage_url = Some(truncate_field(val));
145 }
146 _ => {}
147 }
148 }
149 }
150 }
151 Ok(Event::Eof) => break,
152 Err(e) => {
153 warn!(
154 "Error parsing XML at position {}: {}",
155 reader.buffer_position(),
156 e
157 );
158 break;
159 }
160 _ => {}
161 }
162 buf.clear();
163 }
164
165 PackageData {
166 package_type: Some(PACKAGE_TYPE),
167 name,
168 version,
169 description,
170 homepage_url,
171 copyright,
172 datasource_id: Some(DatasourceId::MicrosoftUpdateManifestMum),
173 ..Default::default()
174 }
175}
176
177crate::register_parser!(
178 "Microsoft Update Manifest .mum file",
179 &["*.mum"],
180 "windows-update",
181 "",
182 None,
183);