1use base64::{engine::general_purpose::STANDARD, Engine};
16use path_absolutize::*;
17use snafu::Snafu;
18use std::path::Path;
19use substring::Substring;
20
21mod crypto;
22mod datetime;
23mod format;
24mod ip;
25
26pub use crypto::{aes_decrypt, aes_encrypt};
27pub use datetime::*;
28pub use format::*;
29pub use ip::IpRules;
30
31#[derive(Debug, Snafu)]
33pub enum Error {
34 #[snafu(display("Encrypt error {message}"))]
35 Aes { message: String },
36 #[snafu(display("Base64 decode {source}"))]
37 Base64Decode { source: base64::DecodeError },
38 #[snafu(display("Invalid {message}"))]
39 Invalid { message: String },
40 #[snafu(display("Io error {source}, {file}"))]
41 Io {
42 source: std::io::Error,
43 file: String,
44 },
45}
46
47type Result<T, E = Error> = std::result::Result<T, E>;
48
49const VERSION: &str = env!("CARGO_PKG_VERSION");
50
51pub fn get_pkg_version() -> &'static str {
53 VERSION
54}
55
56pub fn get_rustc_version() -> String {
58 rustc_version_runtime::version().to_string()
59}
60
61pub fn resolve_path(path: &str) -> String {
71 if path.is_empty() {
72 return "".to_string();
73 }
74 let mut p = path.to_string();
75 if p.starts_with('~') {
76 if let Some(home) = dirs::home_dir() {
77 p = home.to_string_lossy().to_string() + p.substring(1, p.len());
78 };
79 }
80 if let Ok(p) = Path::new(&p).absolutize() {
81 p.to_string_lossy().to_string()
82 } else {
83 p
84 }
85}
86
87pub fn is_pem(value: &str) -> bool {
96 value.starts_with("-----")
97}
98
99pub fn convert_pem(value: &str) -> Result<Vec<u8>> {
108 let buf = if is_pem(value) {
109 value.as_bytes().to_vec()
110 } else if Path::new(&resolve_path(value)).is_file() {
111 std::fs::read(resolve_path(value)).map_err(|e| Error::Io {
112 source: e,
113 file: value.to_string(),
114 })?
115 } else {
116 base64_decode(value).map_err(|e| Error::Base64Decode { source: e })?
117 };
118 Ok(buf)
119}
120
121pub fn convert_certificate_bytes(value: Option<&str>) -> Option<Vec<u8>> {
130 if let Some(value) = value {
131 return convert_pem(value).ok();
132 }
133 None
134}
135
136pub fn base64_encode<T: AsRef<[u8]>>(data: T) -> String {
137 STANDARD.encode(data)
138}
139
140pub fn base64_decode<T: AsRef<[u8]>>(
141 data: T,
142) -> Result<Vec<u8>, base64::DecodeError> {
143 STANDARD.decode(data)
144}
145
146pub fn toml_omit_empty_value(value: &str) -> Result<String, Error> {
154 let mut data =
155 toml::from_str::<toml::Table>(value).map_err(|e| Error::Invalid {
156 message: e.to_string(),
157 })?;
158 let mut omit_keys = vec![];
159 for (key, value) in data.iter() {
160 let Some(table) = value.as_table() else {
161 omit_keys.push(key.to_string());
162 continue;
163 };
164 if table.keys().len() == 0 {
165 omit_keys.push(key.to_string());
166 continue;
167 }
168 }
169 for key in omit_keys {
170 data.remove(&key);
171 }
172 toml::to_string_pretty(&data).map_err(|e| Error::Invalid {
173 message: e.to_string(),
174 })
175}
176
177pub fn path_join(value1: &str, value2: &str) -> String {
187 let end_slash = value1.ends_with("/");
188 let start_slash = value2.starts_with("/");
189 if end_slash && start_slash {
190 format!("{value1}{}", value2.substring(1, value2.len()))
191 } else if end_slash || start_slash {
192 format!("{value1}{value2}")
193 } else {
194 format!("{value1}/{value2}")
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use crate::base64_encode;
202 use pretty_assertions::assert_eq;
203 use std::io::Write;
204 use tempfile::NamedTempFile;
205
206 #[test]
207 fn test_get_pkg_info() {
208 assert_eq!(false, get_pkg_version().is_empty());
209 }
210
211 #[test]
212 fn test_resolve_path() {
213 assert_eq!(
214 dirs::home_dir().unwrap().to_string_lossy(),
215 resolve_path("~/")
216 );
217 }
218 #[test]
219 fn test_get_rustc_version() {
220 assert_eq!(false, get_rustc_version().is_empty());
221 }
222
223 #[test]
224 fn test_path_join() {
225 assert_eq!("a/b", path_join("a", "b"));
226 assert_eq!("a/b", path_join("a/", "b"));
227 assert_eq!("a/b", path_join("a", "/b"));
228 assert_eq!("a/b", path_join("a/", "/b"));
229 }
230
231 #[test]
232 fn test_toml_omit_empty_value() {
233 let data = r###"
234 [upstreams.charts]
235 addrs = ["127.0.0.1:5000", "127.0.0.1:5001 10"]
236 [locations]
237 "###;
238 let result = toml_omit_empty_value(data).unwrap();
239 assert_eq!(
240 result,
241 r###"[upstreams.charts]
242addrs = [
243 "127.0.0.1:5000",
244 "127.0.0.1:5001 10",
245]
246"###
247 );
248 }
249
250 #[test]
251 fn test_convert_certificate_bytes() {
252 let pem = r###"-----BEGIN CERTIFICATE-----
254MIID/TCCAmWgAwIBAgIQJUGCkB1VAYha6fGExkx0KTANBgkqhkiG9w0BAQsFADBV
255MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExFTATBgNVBAsMDHZpY2Fu
256c29AdHJlZTEcMBoGA1UEAwwTbWtjZXJ0IHZpY2Fuc29AdHJlZTAeFw0yNDA3MDYw
257MjIzMzZaFw0yNjEwMDYwMjIzMzZaMEAxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9w
258bWVudCBjZXJ0aWZpY2F0ZTEVMBMGA1UECwwMdmljYW5zb0B0cmVlMIIBIjANBgkq
259hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv5dbylSPQNARrpT/Rn7qZf6JmH3cueMp
260YdOpctuPYeefT0Jdgp67bg17fU5pfyR2BWYdwyvHCNmKqLdYPx/J69hwTiVFMOcw
261lVQJjbzSy8r5r2cSBMMsRaAZopRDnPy7Ls7Ji+AIT4vshUgL55eR7ACuIJpdtUYm
262TzMx9PTA0BUDkit6z7bTMaEbjDmciIBDfepV4goHmvyBJoYMIjnAwnTFRGRs/QJN
263d2ikFq999fRINzTDbRDP1K0Kk6+zYoFAiCMs9lEDymu3RmiWXBXpINR/Sv8CXtz2
2649RTVwTkjyiMOPY99qBfaZTiy+VCjcwTGKPyus1axRMff4xjgOBewOwIDAQABo14w
265XDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0jBBgw
266FoAUhU5Igu3uLUabIqUhUpVXjk1JVtkwFAYDVR0RBA0wC4IJcGluZ2FwLmlvMA0G
267CSqGSIb3DQEBCwUAA4IBgQDBimRKrqnEG65imKriM2QRCEfdB6F/eP9HYvPswuAP
268tvQ6m19/74qbtkd6vjnf6RhMbj9XbCcAJIhRdnXmS0vsBrLDsm2q98zpg6D04F2E
269L++xTiKU6F5KtejXcTHHe23ZpmD2XilwcVDeGFu5BEiFoRH9dmqefGZn3NIwnIeD
270Yi31/cL7BoBjdWku5Qm2nCSWqy12ywbZtQCbgbzb8Me5XZajeGWKb8r6D0Nb+9I9
271OG7dha1L3kxerI5VzVKSiAdGU0C+WcuxfsKAP8ajb1TLOlBaVyilfqmiF457yo/2
272PmTYzMc80+cQWf7loJPskyWvQyfmAnSUX0DI56avXH8LlQ57QebllOtKgMiCo7cr
273CCB2C+8hgRNG9ZmW1KU8rxkzoddHmSB8d6+vFqOajxGdyOV+aX00k3w6FgtHOoKD
274Ztdj1N0eTfn02pibVcXXfwESPUzcjERaMAGg1hoH1F4Gxg0mqmbySAuVRqNLnXp5
275CRVQZGgOQL6WDg3tUUDXYOs=
276-----END CERTIFICATE-----"###;
277 let result = convert_certificate_bytes(Some(pem));
279 assert_eq!(true, result.is_some());
280
281 let mut tmp = NamedTempFile::new().unwrap();
282
283 tmp.write_all(pem.as_bytes()).unwrap();
284
285 let result = convert_certificate_bytes(
286 Some(tmp.path().to_string_lossy()).as_deref(),
287 );
288 assert_eq!(true, result.is_some());
289
290 let data = base64_encode(pem.as_bytes());
291 assert_eq!(1924, data.len());
292 let result = convert_certificate_bytes(Some(data).as_deref());
293 assert_eq!(true, result.is_some());
294 }
295}