verifyos_cli/rules/
app_icon.rs1use crate::rules::core::{
2 AppStoreRule, ArtifactContext, RuleCategory, RuleError, RuleReport, RuleStatus, Severity,
3};
4use std::path::Path;
5
6pub struct AppIconAlphaRule;
7
8impl AppStoreRule for AppIconAlphaRule {
9 fn id(&self) -> &'static str {
10 "RULE_APP_ICON_ALPHA"
11 }
12
13 fn name(&self) -> &'static str {
14 "App Icon Alpha Channel Check"
15 }
16
17 fn category(&self) -> RuleCategory {
18 RuleCategory::Metadata
19 }
20
21 fn severity(&self) -> Severity {
22 Severity::Error
23 }
24
25 fn recommendation(&self) -> &'static str {
26 "Remove the alpha channel from your app icon. App Store icons must be opaque."
27 }
28
29 fn evaluate(&self, artifact: &ArtifactContext) -> Result<RuleReport, RuleError> {
30 let Some(plist) = artifact.info_plist else {
31 return Ok(RuleReport {
32 status: RuleStatus::Skip,
33 message: Some("Info.plist not found".to_string()),
34 evidence: None,
35 });
36 };
37
38 let icon_names = plist.get_app_icons();
39 if icon_names.is_empty() {
40 return Ok(RuleReport {
41 status: RuleStatus::Skip,
42 message: Some("No app icons found in Info.plist".to_string()),
43 evidence: None,
44 });
45 }
46
47 let mut alpha_icons = Vec::new();
48 let all_files = artifact.bundle_file_paths();
49
50 for name in icon_names {
51 for file_path in &all_files {
54 let file_name = file_path.file_name().and_then(|n| n.to_str()).unwrap_or("");
55 if file_name.starts_with(&name) && file_name.ends_with(".png") {
56 if let Ok(has_alpha) = check_png_alpha(file_path) {
57 if has_alpha {
58 alpha_icons.push(file_name.to_string());
59 }
60 }
61 }
62 }
63 }
64
65 if alpha_icons.is_empty() {
66 return Ok(RuleReport {
67 status: RuleStatus::Pass,
68 message: Some("No alpha channel detected in app icons".to_string()),
69 evidence: None,
70 });
71 }
72
73 alpha_icons.sort();
74 alpha_icons.dedup();
75
76 Ok(RuleReport {
77 status: RuleStatus::Fail,
78 message: Some("App icons contain alpha channel (transparency)".to_string()),
79 evidence: Some(format!("Icons with alpha: {}", alpha_icons.join(", "))),
80 })
81 }
82}
83
84fn check_png_alpha(path: &Path) -> std::io::Result<bool> {
85 let bytes = std::fs::read(path)?;
86 if bytes.len() < 26 {
87 return Ok(false);
88 }
89
90 if &bytes[0..8] != b"\x89PNG\r\n\x1a\n" {
92 return Ok(false);
93 }
94
95 if &bytes[12..16] != b"IHDR" {
97 return Ok(false);
98 }
99
100 let color_type = bytes[25];
102 match color_type {
103 4 | 6 => Ok(true), 3 => {
105 Ok(bytes.windows(4).any(|w| w == b"tRNS"))
107 }
108 _ => Ok(false),
109 }
110}