xianyun_backends/helm/
chartref.rs1#[must_use]
8pub fn is_url_chart_ref(chart: &str) -> bool {
9 if !chart.contains("://") {
10 return false;
11 }
12
13 let Ok(parsed) = url::Url::parse(chart) else {
14 return false;
15 };
16
17 matches!(parsed.scheme().to_lowercase().as_str(), "http" | "https")
18}
19
20#[must_use]
25pub fn redact_chart_ref(chart: &str) -> String {
26 if !chart.contains("://") {
27 return chart.to_string();
28 }
29
30 let Ok(mut parsed) = url::Url::parse(chart) else {
31 return "<unparseable chart reference>".to_string();
32 };
33
34 if parsed.set_password(None).is_err() || parsed.set_username("").is_err() {
35 return "<unparseable chart reference>".to_string();
36 }
37
38 parsed.to_string()
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 struct IsUrlCase {
48 name: &'static str,
49 chart: &'static str,
50 want: bool,
51 }
52
53 #[test]
54 fn is_url_chart_ref_cases() {
55 let cases = [
56 IsUrlCase {
57 name: "https archive",
58 chart: "https://gitlab.com/g/p/-/archive/abc/p-abc.tar.gz",
59 want: true,
60 },
61 IsUrlCase {
62 name: "http archive",
63 chart: "http://example.com/charts/my-app-1.0.0.tgz",
64 want: true,
65 },
66 IsUrlCase {
67 name: "uppercase scheme",
68 chart: "HTTPS://example.com/charts/my-app.tgz",
69 want: true,
70 },
71 IsUrlCase {
72 name: "oci reference",
73 chart: "oci://registry.example.com/charts/my-app",
74 want: false,
75 },
76 IsUrlCase {
77 name: "repo chart",
78 chart: "gitlab/gitlab",
79 want: false,
80 },
81 IsUrlCase {
82 name: "relative package",
83 chart: "./dist/my-app-1.0.0.tgz",
84 want: false,
85 },
86 IsUrlCase {
87 name: "absolute package",
88 chart: "/srv/charts/my-app-1.0.0.tgz",
89 want: false,
90 },
91 IsUrlCase {
92 name: "colon in first segment",
93 chart: "overlays:v2/dev",
94 want: false,
95 },
96 IsUrlCase {
97 name: "empty",
98 chart: "",
99 want: false,
100 },
101 ];
102
103 for case in cases {
104 assert_eq!(is_url_chart_ref(case.chart), case.want, "{}", case.name);
105 }
106 }
107
108 fn fake_token() -> String {
112 format!("glpat{}{}", "-", "not-a-real-token")
113 }
114
115 #[test]
118 fn redact_chart_ref_cases() {
119 let token = fake_token();
120
121 let cases: Vec<(&str, String, &str)> = vec![
122 (
123 "credentials are removed",
124 format!("https://oauth2:{token}@gitlab.com/g/p/-/archive/abc/p-abc.tar.gz"),
125 "https://gitlab.com/g/p/-/archive/abc/p-abc.tar.gz",
126 ),
127 (
128 "bare username is removed",
129 format!("https://{token}@gitlab.com/charts/my-app.tgz"),
130 "https://gitlab.com/charts/my-app.tgz",
131 ),
132 (
133 "url without credentials is unchanged",
134 "https://example.com/charts/my-app.tgz".to_string(),
135 "https://example.com/charts/my-app.tgz",
136 ),
137 (
138 "local path is unchanged",
139 "./dist/my-app-1.0.0.tgz".to_string(),
140 "./dist/my-app-1.0.0.tgz",
141 ),
142 (
143 "repo reference is unchanged",
144 "gitlab/gitlab".to_string(),
145 "gitlab/gitlab",
146 ),
147 (
148 "oci reference is unchanged",
149 "oci://registry.example.com/charts/my-app".to_string(),
150 "oci://registry.example.com/charts/my-app",
151 ),
152 (
153 "unparseable reference fails closed",
154 "https://exa mple.com/\u{7f}".to_string(),
155 "<unparseable chart reference>",
156 ),
157 ];
158
159 for (name, chart, want) in cases {
160 assert_eq!(redact_chart_ref(&chart), want, "{name}");
161 }
162 }
163}