1use std::collections::HashMap;
5use std::fs::{self, File};
6use std::io::{Cursor, Read};
7use std::path::Path;
8
9use prost::Message;
10use quick_xml::Reader;
11use quick_xml::XmlVersion;
12use quick_xml::events::Event;
13use rusty_axml::{find_nodes_by_type, get_requested_permissions, parse_from_reader};
14use zip::ZipArchive;
15
16use crate::models::{DatasourceId, PackageData, PackageType};
17use crate::parser_warn as warn;
18use crate::parsers::utils::{
19 CappedIterExt, MAX_ITERATION_COUNT, MAX_MANIFEST_SIZE, truncate_field,
20};
21use crate::utils::magic;
22
23use super::PackageParser;
24use super::metadata::ParserMetadata;
25
26const PACKAGE_TYPE: PackageType = PackageType::Android;
27const MAX_ARCHIVE_SIZE: u64 = 100 * 1024 * 1024;
28const MAX_FILE_SIZE: u64 = 50 * 1024 * 1024;
29const MAX_TOTAL_UNCOMPRESSED_SIZE: u64 = 1024 * 1024 * 1024;
30const MAX_COMPRESSION_RATIO: f64 = 100.0;
31const ANDROID_XML_NAMESPACE: &str = "http://schemas.android.com/apk/res/android";
32
33fn default_package_data(datasource_id: DatasourceId) -> PackageData {
34 PackageData {
35 package_type: Some(PACKAGE_TYPE),
36 datasource_id: Some(datasource_id),
37 ..Default::default()
38 }
39}
40
41pub struct AndroidSoongMetadataParser;
42pub struct AndroidManifestParser;
43pub struct AndroidApkParser;
44pub struct AndroidAabParser;
45
46fn looks_like_android_soong_metadata_content(content: &str) -> bool {
47 let mut saw_named_field = false;
48
49 for line in content.lines().take(40) {
50 let trimmed = line.trim();
51
52 if trimmed.is_empty() || trimmed.starts_with('#') {
53 continue;
54 }
55
56 if trimmed.starts_with("//") {
57 return false;
58 }
59
60 if trimmed.starts_with("third_party {")
61 || trimmed.starts_with("third_party{")
62 || trimmed.starts_with("url {")
63 || trimmed.starts_with("url{")
64 || trimmed.starts_with("identifier {")
65 || trimmed.starts_with("identifier{")
66 || trimmed.starts_with("security {")
67 || trimmed.starts_with("security{")
68 || trimmed.starts_with("last_upgrade_date {")
69 || trimmed.starts_with("last_upgrade_date{")
70 {
71 return true;
72 }
73
74 if let Some(value) = trimmed.strip_prefix("license_type:") {
75 let value = value.trim();
76 if !value.is_empty()
77 && value
78 .chars()
79 .all(|character| character.is_ascii_uppercase() || character == '_')
80 {
81 return true;
82 }
83 }
84
85 if trimmed.starts_with("name:")
86 || trimmed.starts_with("description:")
87 || trimmed.starts_with("homepage:")
88 {
89 saw_named_field = true;
90 }
91 }
92
93 saw_named_field && content.contains("third_party")
94}
95
96impl PackageParser for AndroidSoongMetadataParser {
97 const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
98
99 fn metadata() -> Vec<ParserMetadata> {
100 vec![ParserMetadata {
101 description: "Android Soong METADATA textproto",
102 file_patterns: &["**/METADATA"],
103 package_type: "android",
104 primary_language: "",
105 documentation_url: Some(
106 "https://android.googlesource.com/platform/build/soong/+/refs/heads/main/compliance/project_metadata_proto/project_metadata.proto",
107 ),
108 }]
109 }
110
111 fn is_match(path: &Path) -> bool {
112 if path.file_name().and_then(|name| name.to_str()) != Some("METADATA") {
113 return false;
114 }
115
116 if !path.is_file() {
117 return false;
118 }
119
120 crate::parsers::utils::read_file_to_string(path, Some(MAX_MANIFEST_SIZE))
121 .map(|content| looks_like_android_soong_metadata_content(&content))
122 .unwrap_or(false)
123 }
124
125 fn extract_packages(path: &Path) -> Vec<PackageData> {
126 let content = match crate::parsers::utils::read_file_to_string(path, None) {
127 Ok(content) => content,
128 Err(error) => {
129 warn!(
130 "Failed to read Android Soong METADATA {:?}: {}",
131 path, error
132 );
133 return vec![default_package_data(DatasourceId::AndroidSoongMetadata)];
134 }
135 };
136
137 parse_soong_metadata(&content).into_iter().collect()
138 }
139}
140
141impl PackageParser for AndroidManifestParser {
142 const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
143
144 fn metadata() -> Vec<ParserMetadata> {
145 vec![ParserMetadata {
146 description: "AndroidManifest.xml metadata (text XML or binary AXML)",
147 file_patterns: &["**/AndroidManifest.xml"],
148 package_type: "android",
149 primary_language: "XML",
150 documentation_url: Some(
151 "https://developer.android.com/guide/topics/manifest/manifest-intro",
152 ),
153 }]
154 }
155
156 fn is_match(path: &Path) -> bool {
157 path.file_name().and_then(|name| name.to_str()) == Some("AndroidManifest.xml")
158 }
159
160 fn extract_packages(path: &Path) -> Vec<PackageData> {
161 let bytes = match read_file_bytes(path, None) {
162 Ok(bytes) => bytes,
163 Err(error) => {
164 warn!("Failed to read AndroidManifest.xml {:?}: {}", path, error);
165 return vec![default_package_data(DatasourceId::AndroidManifestXml)];
166 }
167 };
168
169 parse_manifest_bytes(
170 &bytes,
171 DatasourceId::AndroidManifestXml,
172 "AndroidManifest.xml",
173 )
174 .into_iter()
175 .collect()
176 }
177}
178
179impl PackageParser for AndroidApkParser {
180 const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
181
182 fn metadata() -> Vec<ParserMetadata> {
183 vec![ParserMetadata {
184 description: "Android APK archive manifest metadata",
185 file_patterns: &["**/*.apk"],
186 package_type: "android",
187 primary_language: "",
188 documentation_url: Some("https://developer.android.com/build/build-for-release"),
189 }]
190 }
191
192 fn is_match(path: &Path) -> bool {
193 path.extension().and_then(|ext| ext.to_str()) == Some("apk") && magic::is_zip(path)
194 }
195
196 fn extract_packages(path: &Path) -> Vec<PackageData> {
197 match read_best_zip_entry(path, |entry_name| {
198 if entry_name == "AndroidManifest.xml" {
199 Some(0)
200 } else {
201 None
202 }
203 }) {
204 Ok(Some((_, bytes))) => {
205 let package_data = parse_binary_manifest_bytes(&bytes, DatasourceId::AndroidApk)
206 .unwrap_or_else(|error| {
207 warn!("Failed to parse APK manifest {:?}: {}", path, error);
208 default_package_data(DatasourceId::AndroidApk)
209 });
210 vec![package_data]
211 }
212 Ok(None) => {
213 log::debug!("Declining .apk without AndroidManifest.xml {:?}", path);
217 Vec::new()
218 }
219 Err(error) => {
220 log::debug!("Declining unreadable .apk archive {:?}: {}", path, error);
223 Vec::new()
224 }
225 }
226 }
227}
228
229impl PackageParser for AndroidAabParser {
230 const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
231
232 fn metadata() -> Vec<ParserMetadata> {
233 vec![ParserMetadata {
234 description: "Android App Bundle (.aab) proto manifest metadata",
235 file_patterns: &["**/*.aab"],
236 package_type: "android",
237 primary_language: "",
238 documentation_url: Some("https://developer.android.com/guide/app-bundle"),
239 }]
240 }
241
242 fn is_match(path: &Path) -> bool {
243 path.extension().and_then(|ext| ext.to_str()) == Some("aab") && magic::is_zip(path)
244 }
245
246 fn extract_packages(path: &Path) -> Vec<PackageData> {
247 match read_best_zip_entry(path, |entry_name| {
248 if entry_name == "base/manifest/AndroidManifest.xml" {
249 Some(0)
250 } else if entry_name.ends_with("/manifest/AndroidManifest.xml") {
251 Some(1)
252 } else {
253 None
254 }
255 }) {
256 Ok(Some((entry_name, bytes))) => {
257 let package_data = parse_proto_manifest_bytes(&bytes).unwrap_or_else(|error| {
258 warn!(
259 "Failed to parse AAB manifest {:?} ({}): {}",
260 path, entry_name, error
261 );
262 default_package_data(DatasourceId::AndroidAab)
263 });
264 vec![package_data]
265 }
266 Ok(None) => {
267 log::debug!(
270 "Declining .aab without proto AndroidManifest.xml {:?}",
271 path
272 );
273 Vec::new()
274 }
275 Err(error) => {
276 log::debug!("Declining unreadable .aab archive {:?}: {}", path, error);
279 Vec::new()
280 }
281 }
282 }
283}
284
285fn read_file_bytes(path: &Path, max_size: Option<u64>) -> Result<Vec<u8>, String> {
286 let limit = max_size.unwrap_or(MAX_MANIFEST_SIZE);
287 let metadata =
288 fs::metadata(path).map_err(|error| format!("Cannot stat file {:?}: {}", path, error))?;
289
290 if metadata.len() > limit {
291 return Err(format!(
292 "File {:?} is {} bytes, exceeding the {} byte limit",
293 path,
294 metadata.len(),
295 limit
296 ));
297 }
298
299 let mut file =
300 File::open(path).map_err(|error| format!("Failed to open {:?}: {}", path, error))?;
301 let mut bytes = Vec::with_capacity(metadata.len() as usize);
302 file.read_to_end(&mut bytes)
303 .map_err(|error| format!("Failed to read {:?}: {}", path, error))?;
304 Ok(bytes)
305}
306
307fn parse_soong_metadata(content: &str) -> Option<PackageData> {
308 let parsed = match parse_textproto_map(content) {
309 Ok(parsed) => parsed,
310 Err(error) => {
311 log::debug!("Declining unparseable Android Soong METADATA: {error}");
316 return None;
317 }
318 };
319
320 let mut package = default_package_data(DatasourceId::AndroidSoongMetadata);
321 package.name = parsed.get_first_string("name").map(truncate_field);
322 package.description = parsed.get_first_string("description").map(truncate_field);
323
324 if let Some(third_party) = parsed.get_first_map("third_party") {
325 package.version = third_party.get_first_string("version").map(truncate_field);
326
327 let url_entries = third_party
328 .get_all_maps("url")
329 .into_iter()
330 .map(|entry| {
331 let type_ = entry.get_first_string("type").map(truncate_field);
332 let value = entry.get_first_string("value").map(truncate_field);
333 (type_, value)
334 })
335 .collect::<Vec<_>>();
336
337 let homepage_url = third_party.get_first_string("homepage").or_else(|| {
338 url_entries
339 .iter()
340 .find(|(type_, _)| {
341 type_
342 .as_deref()
343 .is_some_and(|type_| type_.eq_ignore_ascii_case("homepage"))
344 })
345 .and_then(|(_, value)| value.clone())
346 });
347 package.homepage_url = homepage_url.map(truncate_field);
348
349 let license_types = third_party
350 .get_all_strings("license_type")
351 .into_iter()
352 .map(truncate_field)
353 .collect::<Vec<_>>();
354 if !license_types.is_empty() {
355 package.extracted_license_statement = Some(license_types.join(", "));
356 }
357
358 let identifiers = third_party
359 .get_all_maps("identifier")
360 .into_iter()
361 .map(|identifier| {
362 let type_ = identifier.get_first_string("type").map(truncate_field);
363 let value = identifier.get_first_string("value").map(truncate_field);
364 let mut object = serde_json::Map::new();
365 if let Some(type_) = type_ {
366 object.insert("type".to_string(), type_.into());
367 }
368 if let Some(value) = &value {
369 object.insert("value".to_string(), value.clone().into());
370 }
371
372 if package.vcs_url.is_none()
373 && let (Some(type_), Some(value)) = (
374 identifier.get_first_string("type"),
375 identifier.get_first_string("value"),
376 )
377 {
378 let lower_type = type_.to_ascii_lowercase();
379 if lower_type.contains("git") {
380 package.vcs_url = Some(truncate_field(value));
381 } else if lower_type.contains("archive")
382 || lower_type.contains("tar")
383 || lower_type.contains("zip")
384 {
385 package.download_url = Some(truncate_field(value));
386 }
387 }
388
389 serde_json::Value::Object(object)
390 })
391 .collect::<Vec<_>>();
392
393 for (type_, value) in &url_entries {
394 let Some(value) = value else {
395 continue;
396 };
397
398 match type_.as_deref().map(str::to_ascii_lowercase).as_deref() {
399 Some("git") if package.vcs_url.is_none() => {
400 package.vcs_url = Some(value.clone());
401 }
402 Some("archive") if package.download_url.is_none() => {
403 package.download_url = Some(value.clone());
404 }
405 Some("homepage") if package.homepage_url.is_none() => {
406 package.homepage_url = Some(value.clone());
407 }
408 _ => {}
409 }
410 }
411
412 let mut extra_data = HashMap::new();
413 if !identifiers.is_empty() {
414 extra_data.insert("identifiers".to_string(), identifiers.into());
415 }
416 if !url_entries.is_empty() {
417 extra_data.insert(
418 "urls".to_string(),
419 url_entries
420 .iter()
421 .map(|(type_, value)| {
422 let mut object = serde_json::Map::new();
423 if let Some(type_) = type_ {
424 object.insert("type".to_string(), type_.clone().into());
425 }
426 if let Some(value) = value {
427 object.insert("value".to_string(), value.clone().into());
428 }
429 serde_json::Value::Object(object)
430 })
431 .collect::<Vec<_>>()
432 .into(),
433 );
434 }
435
436 if let Some(last_upgrade_date) = third_party.get_first_map("last_upgrade_date") {
437 let year = last_upgrade_date.get_first_string("year");
438 let month = last_upgrade_date.get_first_string("month");
439 let day = last_upgrade_date.get_first_string("day");
440 if let (Some(year), Some(month), Some(day)) = (year, month, day) {
441 let formatted = format!(
442 "{:04}-{:02}-{:02}",
443 year.parse::<u32>().unwrap_or_default(),
444 month.parse::<u32>().unwrap_or_default(),
445 day.parse::<u32>().unwrap_or_default()
446 );
447 extra_data.insert(
448 "last_upgrade_date".to_string(),
449 truncate_field(formatted).into(),
450 );
451 }
452 }
453
454 if let Some(upstream_url) = third_party.get_first_string("url") {
455 extra_data.insert(
456 "upstream_url".to_string(),
457 truncate_field(upstream_url).into(),
458 );
459 }
460
461 if !extra_data.is_empty() {
462 package.extra_data = Some(extra_data);
463 }
464 }
465
466 Some(package)
467}
468
469const BINARY_AXML_MAGIC: [u8; 4] = [0x03, 0x00, 0x08, 0x00];
472
473const UTF8_BOM: [u8; 3] = [0xEF, 0xBB, 0xBF];
475
476fn parse_manifest_bytes(
477 bytes: &[u8],
478 datasource_id: DatasourceId,
479 context: &str,
480) -> Option<PackageData> {
481 if looks_like_binary_axml(bytes) {
482 return parse_binary_manifest_bytes(bytes, datasource_id)
483 .map(Some)
484 .unwrap_or_else(|error| {
485 warn!(
486 "Failed to parse {} as binary Android XML: {}",
487 context, error
488 );
489 None
490 });
491 }
492
493 if looks_like_text_xml(bytes) {
494 return match parse_text_manifest_bytes(bytes, datasource_id) {
495 Ok(package) => Some(package),
496 Err(error) => {
497 warn!("Failed to parse {} as text XML: {}", context, error);
498 None
499 }
500 };
501 }
502
503 log::debug!(
507 "Declining unrecognized {} content (neither binary AXML nor text XML)",
508 context
509 );
510 None
511}
512
513fn looks_like_binary_axml(bytes: &[u8]) -> bool {
514 bytes.starts_with(&BINARY_AXML_MAGIC)
515}
516
517fn looks_like_text_xml(bytes: &[u8]) -> bool {
518 let bytes = bytes.strip_prefix(&UTF8_BOM).unwrap_or(bytes);
519 bytes
520 .iter()
521 .find(|byte| !byte.is_ascii_whitespace())
522 .is_some_and(|byte| *byte == b'<')
523}
524
525fn parse_text_manifest_bytes(
526 bytes: &[u8],
527 datasource_id: DatasourceId,
528) -> Result<PackageData, String> {
529 let content = String::from_utf8(bytes.to_vec())
530 .map_err(|error| format!("Invalid UTF-8 in AndroidManifest.xml: {}", error))?;
531
532 let mut reader = Reader::from_str(&content);
533 reader.config_mut().trim_text(true);
534
535 let mut buf = Vec::new();
536 let mut manifest_attributes = HashMap::new();
537 let mut uses_sdk_attributes = HashMap::new();
538 let mut application_attributes = HashMap::new();
539 let mut requested_permissions = Vec::new();
540 let mut uses_libraries = Vec::new();
541 let mut iteration_count = 0usize;
542
543 loop {
544 iteration_count += 1;
545 if iteration_count > MAX_ITERATION_COUNT {
546 return Err(format!(
547 "Exceeded MAX_ITERATION_COUNT ({}) while parsing AndroidManifest.xml",
548 MAX_ITERATION_COUNT
549 ));
550 }
551
552 match reader.read_event_into(&mut buf) {
553 Ok(Event::Start(event)) | Ok(Event::Empty(event)) => {
554 let name = String::from_utf8_lossy(event.name().as_ref()).into_owned();
555 let attributes = xml_attributes_to_map(&reader, &event)?;
556 match name.as_str() {
557 "manifest" if manifest_attributes.is_empty() => {
558 manifest_attributes = attributes
559 }
560 "uses-sdk" => uses_sdk_attributes = attributes,
561 "application" if application_attributes.is_empty() => {
562 application_attributes = attributes;
563 }
564 "uses-permission" | "uses-permission-sdk-23" => {
565 if let Some(permission) = attributes.get("android:name") {
566 requested_permissions.push(permission.clone());
567 }
568 }
569 "uses-library" => {
570 if let Some(library_name) = attributes.get("android:name") {
571 uses_libraries.push(library_name.clone());
572 }
573 }
574 _ => {}
575 }
576 }
577 Ok(Event::Eof) => break,
578 Err(error) => {
579 return Err(format!(
580 "XML parse error at position {}: {}",
581 reader.buffer_position(),
582 error
583 ));
584 }
585 _ => {}
586 }
587
588 buf.clear();
589 }
590
591 Ok(build_manifest_package_data(
592 datasource_id,
593 &manifest_attributes,
594 &uses_sdk_attributes,
595 &application_attributes,
596 requested_permissions,
597 uses_libraries,
598 ))
599}
600
601fn xml_attributes_to_map(
602 reader: &Reader<&[u8]>,
603 event: &quick_xml::events::BytesStart<'_>,
604) -> Result<HashMap<String, String>, String> {
605 let mut attributes = HashMap::new();
606
607 for attribute in event
608 .attributes()
609 .flatten()
610 .capped("AndroidManifest.xml attributes")
611 {
612 let key = String::from_utf8_lossy(attribute.key.as_ref()).into_owned();
613 let value = attribute
614 .decoded_and_normalized_value(XmlVersion::Implicit1_0, reader.decoder())
615 .map_err(|error| format!("Failed to decode XML attribute {}: {}", key, error))?
616 .into_owned();
617 attributes.insert(key, truncate_field(value));
618 }
619
620 Ok(attributes)
621}
622
623fn parse_binary_manifest_bytes(
624 bytes: &[u8],
625 datasource_id: DatasourceId,
626) -> Result<PackageData, String> {
627 let axml = std::panic::catch_unwind(|| parse_from_reader(Cursor::new(bytes.to_vec())))
628 .map_err(|_| "rusty-axml panicked while parsing binary Android XML".to_string())?
629 .map_err(|error| format!("rusty-axml parse failure: {}", error))?;
630
631 let manifest_attributes =
632 normalize_binary_attributes(axml.root().borrow().attributes().clone());
633 let uses_sdk_attributes = find_nodes_by_type(&axml, "uses-sdk")
634 .into_iter()
635 .next()
636 .map(|node| normalize_binary_attributes(node.borrow().attributes().clone()))
637 .unwrap_or_default();
638 let application_attributes = find_nodes_by_type(&axml, "application")
639 .into_iter()
640 .next()
641 .map(|node| normalize_binary_attributes(node.borrow().attributes().clone()))
642 .unwrap_or_default();
643
644 let requested_permissions = get_requested_permissions(&axml)
645 .into_iter()
646 .map(truncate_field)
647 .collect::<Vec<_>>();
648 let uses_libraries = find_nodes_by_type(&axml, "uses-library")
649 .into_iter()
650 .filter_map(|node| node.borrow().get_attr("android:name").map(str::to_string))
651 .map(truncate_field)
652 .collect::<Vec<_>>();
653
654 Ok(build_manifest_package_data(
655 datasource_id,
656 &manifest_attributes,
657 &uses_sdk_attributes,
658 &application_attributes,
659 requested_permissions,
660 uses_libraries,
661 ))
662}
663
664fn build_manifest_package_data(
665 datasource_id: DatasourceId,
666 manifest_attributes: &HashMap<String, String>,
667 uses_sdk_attributes: &HashMap<String, String>,
668 application_attributes: &HashMap<String, String>,
669 requested_permissions: Vec<String>,
670 uses_libraries: Vec<String>,
671) -> PackageData {
672 let mut package = default_package_data(datasource_id);
673 package.name = manifest_attributes.get("package").cloned();
674 package.version = manifest_attributes
675 .get("android:versionName")
676 .cloned()
677 .or_else(|| manifest_attributes.get("android:versionCode").cloned());
678
679 package.description = application_attributes
680 .get("android:label")
681 .filter(|label| {
682 !label.starts_with('@') && !label.chars().all(|character| character.is_ascii_digit())
683 })
684 .cloned();
685
686 let mut extra_data = HashMap::new();
687 insert_extra(
688 &mut extra_data,
689 "version_code",
690 manifest_attributes.get("android:versionCode"),
691 );
692 insert_extra(
693 &mut extra_data,
694 "compile_sdk_version",
695 manifest_attributes.get("android:compileSdkVersion"),
696 );
697 insert_extra(
698 &mut extra_data,
699 "compile_sdk_version_codename",
700 manifest_attributes.get("android:compileSdkVersionCodename"),
701 );
702 insert_extra(
703 &mut extra_data,
704 "platform_build_version_code",
705 manifest_attributes.get("platformBuildVersionCode"),
706 );
707 insert_extra(
708 &mut extra_data,
709 "platform_build_version_name",
710 manifest_attributes.get("platformBuildVersionName"),
711 );
712 insert_extra(
713 &mut extra_data,
714 "min_sdk_version",
715 uses_sdk_attributes.get("android:minSdkVersion"),
716 );
717 insert_extra(
718 &mut extra_data,
719 "target_sdk_version",
720 uses_sdk_attributes.get("android:targetSdkVersion"),
721 );
722 insert_extra(
723 &mut extra_data,
724 "max_sdk_version",
725 uses_sdk_attributes.get("android:maxSdkVersion"),
726 );
727
728 if !requested_permissions.is_empty() {
729 extra_data.insert(
730 "requested_permissions".to_string(),
731 requested_permissions
732 .into_iter()
733 .map(serde_json::Value::from)
734 .collect::<Vec<_>>()
735 .into(),
736 );
737 }
738 if !uses_libraries.is_empty() {
739 extra_data.insert(
740 "uses_libraries".to_string(),
741 uses_libraries
742 .into_iter()
743 .map(serde_json::Value::from)
744 .collect::<Vec<_>>()
745 .into(),
746 );
747 }
748
749 if !extra_data.is_empty() {
750 package.extra_data = Some(extra_data);
751 }
752
753 package
754}
755
756fn normalize_binary_attributes(attributes: HashMap<String, String>) -> HashMap<String, String> {
757 attributes
758 .into_iter()
759 .map(|(key, value)| (key, normalize_binary_attribute_value(&value)))
760 .collect()
761}
762
763fn normalize_binary_attribute_value(value: &str) -> String {
764 let hex_value = value
765 .strip_prefix("(type 0x10) 0x")
766 .or_else(|| value.strip_prefix("0x"));
767
768 if let Some(hex_value) = hex_value
769 && let Ok(parsed) = u64::from_str_radix(hex_value, 16)
770 {
771 return parsed.to_string();
772 }
773
774 value.to_string()
775}
776
777fn insert_extra(
778 extra_data: &mut HashMap<String, serde_json::Value>,
779 key: &str,
780 value: Option<&String>,
781) {
782 if let Some(value) = value {
783 extra_data.insert(key.to_string(), truncate_field(value.clone()).into());
784 }
785}
786
787fn read_best_zip_entry<F>(
788 path: &Path,
789 mut rank_entry: F,
790) -> Result<Option<(String, Vec<u8>)>, String>
791where
792 F: FnMut(&str) -> Option<u8>,
793{
794 let metadata = fs::metadata(path)
795 .map_err(|error| format!("Failed to stat archive {:?}: {}", path, error))?;
796 if metadata.len() > MAX_ARCHIVE_SIZE {
797 return Err(format!(
798 "Archive {:?} is {} bytes, exceeding the {} byte limit",
799 path,
800 metadata.len(),
801 MAX_ARCHIVE_SIZE
802 ));
803 }
804
805 let file = File::open(path)
806 .map_err(|error| format!("Failed to open archive {:?}: {}", path, error))?;
807 let mut archive = ZipArchive::new(file)
808 .map_err(|error| format!("Failed to parse ZIP archive {:?}: {}", path, error))?;
809
810 let mut total_uncompressed = 0u64;
811 let mut best: Option<(u8, String, Vec<u8>)> = None;
812 let entry_count = archive.len().min(MAX_ITERATION_COUNT);
813
814 if archive.len() > MAX_ITERATION_COUNT {
815 warn!(
816 "Archive {:?} has more than MAX_ITERATION_COUNT ({}) entries; truncating scan",
817 path, MAX_ITERATION_COUNT
818 );
819 }
820
821 for index in 0..entry_count {
822 let mut entry = archive.by_index(index).map_err(|error| {
823 format!(
824 "Failed to read ZIP entry {} in {:?}: {}",
825 index, path, error
826 )
827 })?;
828
829 total_uncompressed = total_uncompressed.saturating_add(entry.size());
830 if total_uncompressed > MAX_TOTAL_UNCOMPRESSED_SIZE {
831 return Err(format!(
832 "Archive {:?} exceeds total uncompressed size limit of {} bytes",
833 path, MAX_TOTAL_UNCOMPRESSED_SIZE
834 ));
835 }
836
837 let entry_name = entry.name().replace('\\', "/");
838 if entry_name.starts_with('/') || entry_name.split('/').any(|segment| segment == "..") {
839 return Err(format!(
840 "Archive entry {} contains a disallowed path",
841 entry_name
842 ));
843 }
844 let Some(rank) = rank_entry(&entry_name) else {
845 continue;
846 };
847
848 if entry.size() > MAX_FILE_SIZE {
849 return Err(format!(
850 "Archive entry {} is {} bytes, exceeding the {} byte limit",
851 entry_name,
852 entry.size(),
853 MAX_FILE_SIZE
854 ));
855 }
856
857 let compressed_size = entry.compressed_size();
858 if compressed_size > 0 {
859 let ratio = entry.size() as f64 / compressed_size as f64;
860 if ratio > MAX_COMPRESSION_RATIO {
861 return Err(format!(
862 "Archive entry {} has suspicious compression ratio {:.2}:1",
863 entry_name, ratio
864 ));
865 }
866 }
867
868 let should_replace = match &best {
869 Some((best_rank, _, _)) => rank < *best_rank,
870 None => true,
871 };
872
873 if should_replace {
874 let mut bytes = Vec::with_capacity(entry.size() as usize);
875 entry.read_to_end(&mut bytes).map_err(|error| {
876 format!("Failed to read archive entry {}: {}", entry_name, error)
877 })?;
878 best = Some((rank, entry_name, bytes));
879 }
880 }
881
882 Ok(best.map(|(_, entry_name, bytes)| (entry_name, bytes)))
883}
884
885fn parse_proto_manifest_bytes(bytes: &[u8]) -> Result<PackageData, String> {
886 let node =
887 ProtoXmlNode::decode(bytes).map_err(|error| format!("prost decode failure: {}", error))?;
888 let root_element = node
889 .element()
890 .ok_or_else(|| "Proto manifest root is not an element".to_string())?;
891 if root_element.name != "manifest" {
892 return Err(format!(
893 "Unexpected proto XML root element: {}",
894 root_element.name
895 ));
896 }
897
898 let manifest_attributes = proto_attributes_to_map(&root_element.attribute);
899 let uses_sdk_attributes = root_element
900 .child_elements_named("uses-sdk")
901 .next()
902 .map(|element| proto_attributes_to_map(&element.attribute))
903 .unwrap_or_default();
904 let application_attributes = root_element
905 .child_elements_named("application")
906 .next()
907 .map(|element| proto_attributes_to_map(&element.attribute))
908 .unwrap_or_default();
909 let requested_permissions = root_element
910 .child_elements_named_any(&["uses-permission", "uses-permission-sdk-23"])
911 .filter_map(|element| proto_attributes_to_map(&element.attribute).remove("android:name"))
912 .collect::<Vec<_>>();
913 let uses_libraries = root_element
914 .child_elements_named("uses-library")
915 .filter_map(|element| proto_attributes_to_map(&element.attribute).remove("android:name"))
916 .collect::<Vec<_>>();
917
918 let mut package = build_manifest_package_data(
919 DatasourceId::AndroidAab,
920 &manifest_attributes,
921 &uses_sdk_attributes,
922 &application_attributes,
923 requested_permissions,
924 uses_libraries,
925 );
926
927 if let Some(extra_data) = package.extra_data.as_mut() {
928 extra_data.insert("manifest_encoding".to_string(), "proto".into());
929 } else {
930 package.extra_data = Some(HashMap::from([(
931 "manifest_encoding".to_string(),
932 serde_json::Value::String("proto".to_string()),
933 )]));
934 }
935
936 Ok(package)
937}
938
939fn proto_attributes_to_map(attributes: &[ProtoXmlAttribute]) -> HashMap<String, String> {
940 attributes
941 .iter()
942 .filter_map(|attribute| {
943 let key = proto_attribute_key(attribute)?;
944 let value = proto_attribute_value(attribute)?;
945 Some((key, truncate_field(value)))
946 })
947 .collect()
948}
949
950fn proto_attribute_key(attribute: &ProtoXmlAttribute) -> Option<String> {
951 if attribute.name.is_empty() {
952 return None;
953 }
954
955 if attribute.namespace_uri == ANDROID_XML_NAMESPACE {
956 return Some(format!("android:{}", attribute.name));
957 }
958
959 Some(attribute.name.clone())
960}
961
962fn proto_attribute_value(attribute: &ProtoXmlAttribute) -> Option<String> {
963 if !attribute.value.is_empty() {
964 return Some(attribute.value.clone());
965 }
966
967 attribute
968 .compiled_item
969 .as_ref()
970 .and_then(proto_item_to_string)
971}
972
973fn proto_item_to_string(item: &ProtoItem) -> Option<String> {
974 match &item.value {
975 Some(proto_item::Value::Str(value)) => Some(value.value.clone()),
976 Some(proto_item::Value::RawStr(value)) => Some(value.value.clone()),
977 Some(proto_item::Value::Prim(value)) => proto_primitive_to_string(value),
978 _ => None,
979 }
980}
981
982fn proto_primitive_to_string(primitive: &ProtoPrimitive) -> Option<String> {
983 match &primitive.value {
984 Some(proto_primitive::Value::IntDecimal(value)) => Some(value.to_string()),
985 Some(proto_primitive::Value::IntHexadecimal(value)) => Some(format!("0x{value:x}")),
986 Some(proto_primitive::Value::Boolean(value)) => Some(value.to_string()),
987 Some(proto_primitive::Value::Float(value)) => Some(value.to_string()),
988 Some(proto_primitive::Value::Dimension(value)) => Some(value.to_string()),
989 Some(proto_primitive::Value::Fraction(value)) => Some(value.to_string()),
990 _ => None,
991 }
992}
993
994#[derive(Debug, Clone, Default)]
995struct ProtoMap {
996 fields: HashMap<String, Vec<ProtoValue>>,
997}
998
999#[derive(Debug, Clone)]
1000enum ProtoValue {
1001 Scalar(String),
1002 Map(ProtoMap),
1003}
1004
1005impl ProtoMap {
1006 fn get_first_string(&self, key: &str) -> Option<String> {
1007 self.fields.get(key).and_then(|values| {
1008 values.iter().find_map(|value| match value {
1009 ProtoValue::Scalar(value) => Some(value.clone()),
1010 ProtoValue::Map(_) => None,
1011 })
1012 })
1013 }
1014
1015 fn get_all_strings(&self, key: &str) -> Vec<String> {
1016 self.fields
1017 .get(key)
1018 .into_iter()
1019 .flatten()
1020 .filter_map(|value| match value {
1021 ProtoValue::Scalar(value) => Some(value.clone()),
1022 ProtoValue::Map(_) => None,
1023 })
1024 .collect()
1025 }
1026
1027 fn get_first_map(&self, key: &str) -> Option<ProtoMap> {
1028 self.fields.get(key).and_then(|values| {
1029 values.iter().find_map(|value| match value {
1030 ProtoValue::Map(value) => Some(value.clone()),
1031 ProtoValue::Scalar(_) => None,
1032 })
1033 })
1034 }
1035
1036 fn get_all_maps(&self, key: &str) -> Vec<ProtoMap> {
1037 self.fields
1038 .get(key)
1039 .into_iter()
1040 .flatten()
1041 .filter_map(|value| match value {
1042 ProtoValue::Map(value) => Some(value.clone()),
1043 ProtoValue::Scalar(_) => None,
1044 })
1045 .collect()
1046 }
1047}
1048
1049fn parse_textproto_map(content: &str) -> Result<ProtoMap, String> {
1050 let mut parser = TextProtoParser::new(content)?;
1051 parser.parse_map(false)
1052}
1053
1054struct TextProtoParser {
1055 tokens: Vec<TextProtoToken>,
1056 position: usize,
1057}
1058
1059#[derive(Debug, Clone)]
1060enum TextProtoToken {
1061 Identifier(String),
1062 String(String),
1063 Colon,
1064 LBrace,
1065 RBrace,
1066}
1067
1068impl TextProtoParser {
1069 fn new(content: &str) -> Result<Self, String> {
1070 Ok(Self {
1071 tokens: tokenize_textproto(content)?,
1072 position: 0,
1073 })
1074 }
1075
1076 fn parse_map(&mut self, stop_on_rbrace: bool) -> Result<ProtoMap, String> {
1077 let mut map = ProtoMap::default();
1078
1079 while let Some(token) = self.peek() {
1080 match token {
1081 TextProtoToken::RBrace if stop_on_rbrace => {
1082 self.position += 1;
1083 break;
1084 }
1085 TextProtoToken::RBrace => return Err("Unexpected closing brace".to_string()),
1086 TextProtoToken::Identifier(_) => {
1087 let key = self.expect_identifier()?;
1088 match self.peek() {
1089 Some(TextProtoToken::Colon) => {
1090 self.position += 1;
1091 match self.peek() {
1092 Some(TextProtoToken::LBrace) => {
1093 self.position += 1;
1094 let value = self.parse_map(true)?;
1095 map.fields
1096 .entry(key)
1097 .or_default()
1098 .push(ProtoValue::Map(value));
1099 }
1100 _ => {
1101 let value = self.expect_scalar()?;
1102 map.fields
1103 .entry(key)
1104 .or_default()
1105 .push(ProtoValue::Scalar(truncate_field(value)));
1106 }
1107 }
1108 }
1109 Some(TextProtoToken::LBrace) => {
1110 self.position += 1;
1111 let value = self.parse_map(true)?;
1112 map.fields
1113 .entry(key)
1114 .or_default()
1115 .push(ProtoValue::Map(value));
1116 }
1117 Some(other) => {
1118 return Err(format!("Unexpected token after key: {:?}", other));
1119 }
1120 None => return Err("Unexpected end of input after key".to_string()),
1121 }
1122 }
1123 other => return Err(format!("Unexpected token in textproto: {:?}", other)),
1124 }
1125 }
1126
1127 Ok(map)
1128 }
1129
1130 fn expect_identifier(&mut self) -> Result<String, String> {
1131 match self.next() {
1132 Some(TextProtoToken::Identifier(value)) => Ok(value),
1133 other => Err(format!("Expected identifier, found {:?}", other)),
1134 }
1135 }
1136
1137 fn expect_scalar(&mut self) -> Result<String, String> {
1138 match self.next() {
1139 Some(TextProtoToken::String(mut value)) => {
1140 while matches!(self.peek(), Some(TextProtoToken::String(_))) {
1141 if let Some(TextProtoToken::String(next)) = self.next() {
1142 value.push_str(&next);
1143 }
1144 }
1145 Ok(value)
1146 }
1147 Some(TextProtoToken::Identifier(value)) => Ok(value),
1148 other => Err(format!("Expected scalar value, found {:?}", other)),
1149 }
1150 }
1151
1152 fn peek(&self) -> Option<&TextProtoToken> {
1153 self.tokens.get(self.position)
1154 }
1155
1156 fn next(&mut self) -> Option<TextProtoToken> {
1157 let token = self.tokens.get(self.position).cloned();
1158 if token.is_some() {
1159 self.position += 1;
1160 }
1161 token
1162 }
1163}
1164
1165fn tokenize_textproto(content: &str) -> Result<Vec<TextProtoToken>, String> {
1166 let mut tokens = Vec::new();
1167 let chars = content.chars().collect::<Vec<_>>();
1168 let mut index = 0usize;
1169
1170 while index < chars.len() {
1171 match chars[index] {
1172 '{' => {
1173 tokens.push(TextProtoToken::LBrace);
1174 index += 1;
1175 }
1176 '}' => {
1177 tokens.push(TextProtoToken::RBrace);
1178 index += 1;
1179 }
1180 ':' => {
1181 tokens.push(TextProtoToken::Colon);
1182 index += 1;
1183 }
1184 '"' => {
1185 index += 1;
1186 let mut value = String::new();
1187 while index < chars.len() {
1188 match chars[index] {
1189 '\\' if index + 1 < chars.len() => {
1190 index += 1;
1191 value.push(chars[index]);
1192 index += 1;
1193 }
1194 '"' => {
1195 index += 1;
1196 break;
1197 }
1198 character => {
1199 value.push(character);
1200 index += 1;
1201 }
1202 }
1203 }
1204 tokens.push(TextProtoToken::String(value));
1205 }
1206 '#' => {
1207 while index < chars.len() && chars[index] != '\n' {
1208 index += 1;
1209 }
1210 }
1211 '/' if index + 1 < chars.len() && chars[index + 1] == '/' => {
1212 index += 2;
1213 while index < chars.len() && chars[index] != '\n' {
1214 index += 1;
1215 }
1216 }
1217 character if character.is_ascii_whitespace() => index += 1,
1218 _ => {
1219 let start = index;
1220 while index < chars.len() {
1221 let character = chars[index];
1222 let starts_comment =
1223 character == '/' && index + 1 < chars.len() && chars[index + 1] == '/';
1224
1225 if character.is_ascii_whitespace()
1226 || matches!(character, '{' | '}' | ':' | '#')
1227 || starts_comment
1228 {
1229 break;
1230 }
1231
1232 index += 1;
1233 }
1234
1235 let token = chars[start..index].iter().collect::<String>();
1236 if token.is_empty() {
1237 return Err("Encountered empty textproto token".to_string());
1238 }
1239 tokens.push(TextProtoToken::Identifier(token));
1240 }
1241 }
1242 }
1243
1244 Ok(tokens)
1245}
1246
1247#[derive(Clone, PartialEq, Message)]
1248pub struct ProtoSourcePosition {
1249 #[prost(uint32, tag = "1")]
1250 pub line_number: u32,
1251 #[prost(uint32, tag = "2")]
1252 pub column_number: u32,
1253}
1254
1255#[derive(Clone, PartialEq, Message)]
1256pub struct ProtoXmlNode {
1257 #[prost(oneof = "proto_xml_node::Node", tags = "1, 2")]
1258 pub node: Option<proto_xml_node::Node>,
1259 #[prost(message, optional, tag = "3")]
1260 pub source: Option<ProtoSourcePosition>,
1261}
1262
1263impl ProtoXmlNode {
1264 fn element(&self) -> Option<&ProtoXmlElement> {
1265 match &self.node {
1266 Some(proto_xml_node::Node::Element(element)) => Some(element),
1267 _ => None,
1268 }
1269 }
1270}
1271
1272pub mod proto_xml_node {
1273 use super::ProtoXmlElement;
1274 use prost::Oneof;
1275
1276 #[derive(Clone, PartialEq, Oneof)]
1277 pub enum Node {
1278 #[prost(message, tag = "1")]
1279 Element(ProtoXmlElement),
1280 #[prost(string, tag = "2")]
1281 Text(String),
1282 }
1283}
1284
1285#[derive(Clone, PartialEq, Message)]
1286pub struct ProtoXmlElement {
1287 #[prost(message, repeated, tag = "1")]
1288 pub namespace_declaration: Vec<ProtoXmlNamespace>,
1289 #[prost(string, tag = "2")]
1290 pub namespace_uri: String,
1291 #[prost(string, tag = "3")]
1292 pub name: String,
1293 #[prost(message, repeated, tag = "4")]
1294 pub attribute: Vec<ProtoXmlAttribute>,
1295 #[prost(message, repeated, tag = "5")]
1296 pub child: Vec<ProtoXmlNode>,
1297}
1298
1299impl ProtoXmlElement {
1300 fn child_elements_named<'a>(
1301 &'a self,
1302 name: &'a str,
1303 ) -> impl Iterator<Item = &'a ProtoXmlElement> {
1304 self.child
1305 .iter()
1306 .filter_map(ProtoXmlNode::element)
1307 .filter(move |element| element.name == name)
1308 }
1309
1310 fn child_elements_named_any<'a>(
1311 &'a self,
1312 names: &'a [&'a str],
1313 ) -> impl Iterator<Item = &'a ProtoXmlElement> {
1314 self.child
1315 .iter()
1316 .filter_map(ProtoXmlNode::element)
1317 .filter(move |element| names.contains(&element.name.as_str()))
1318 }
1319}
1320
1321#[derive(Clone, PartialEq, Message)]
1322pub struct ProtoXmlNamespace {
1323 #[prost(string, tag = "1")]
1324 pub prefix: String,
1325 #[prost(string, tag = "2")]
1326 pub uri: String,
1327 #[prost(message, optional, tag = "3")]
1328 pub source: Option<ProtoSourcePosition>,
1329}
1330
1331#[derive(Clone, PartialEq, Message)]
1332pub struct ProtoXmlAttribute {
1333 #[prost(string, tag = "1")]
1334 pub namespace_uri: String,
1335 #[prost(string, tag = "2")]
1336 pub name: String,
1337 #[prost(string, tag = "3")]
1338 pub value: String,
1339 #[prost(message, optional, tag = "4")]
1340 pub source: Option<ProtoSourcePosition>,
1341 #[prost(uint32, tag = "5")]
1342 pub resource_id: u32,
1343 #[prost(message, optional, tag = "6")]
1344 pub compiled_item: Option<ProtoItem>,
1345}
1346
1347#[derive(Clone, PartialEq, Message)]
1348pub struct ProtoItem {
1349 #[prost(oneof = "proto_item::Value", tags = "2, 3, 7")]
1350 pub value: Option<proto_item::Value>,
1351 #[prost(uint32, tag = "8")]
1352 pub flag_status: u32,
1353 #[prost(bool, tag = "9")]
1354 pub flag_negated: bool,
1355 #[prost(string, tag = "10")]
1356 pub flag_name: String,
1357}
1358
1359pub mod proto_item {
1360 use super::{ProtoPrimitive, ProtoRawStringValue, ProtoStringValue};
1361 use prost::Oneof;
1362
1363 #[derive(Clone, PartialEq, Oneof)]
1364 pub enum Value {
1365 #[prost(message, tag = "2")]
1366 Str(ProtoStringValue),
1367 #[prost(message, tag = "3")]
1368 RawStr(ProtoRawStringValue),
1369 #[prost(message, tag = "7")]
1370 Prim(ProtoPrimitive),
1371 }
1372}
1373
1374#[derive(Clone, PartialEq, Message)]
1375pub struct ProtoStringValue {
1376 #[prost(string, tag = "1")]
1377 pub value: String,
1378}
1379
1380#[derive(Clone, PartialEq, Message)]
1381pub struct ProtoRawStringValue {
1382 #[prost(string, tag = "1")]
1383 pub value: String,
1384}
1385
1386#[derive(Clone, PartialEq, Message)]
1387pub struct ProtoPrimitive {
1388 #[prost(oneof = "proto_primitive::Value", tags = "3, 6, 7, 8, 13, 14")]
1389 pub value: Option<proto_primitive::Value>,
1390}
1391
1392pub mod proto_primitive {
1393 use prost::Oneof;
1394
1395 #[derive(Clone, PartialEq, Oneof)]
1396 pub enum Value {
1397 #[prost(float, tag = "3")]
1398 Float(f32),
1399 #[prost(int32, tag = "6")]
1400 IntDecimal(i32),
1401 #[prost(uint32, tag = "7")]
1402 IntHexadecimal(u32),
1403 #[prost(bool, tag = "8")]
1404 Boolean(bool),
1405 #[prost(uint32, tag = "13")]
1406 Dimension(u32),
1407 #[prost(uint32, tag = "14")]
1408 Fraction(u32),
1409 }
1410}