diff --git a/kube-client/src/config/file_config.rs b/kube-client/src/config/file_config.rs index dd41cc5db..c290289d5 100644 --- a/kube-client/src/config/file_config.rs +++ b/kube-client/src/config/file_config.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - fs, + fs, io, path::{Path, PathBuf}, }; @@ -351,8 +351,8 @@ const KUBECONFIG: &str = "KUBECONFIG"; impl Kubeconfig { /// Read a Config from an arbitrary location pub fn read_from>(path: P) -> Result { - let data = fs::read_to_string(&path) - .map_err(|source| KubeconfigError::ReadConfig(source, path.as_ref().into()))?; + let data = + read_path(&path).map_err(|source| KubeconfigError::ReadConfig(source, path.as_ref().into()))?; // Remap all files we read to absolute paths. let mut merged_docs = None; @@ -497,6 +497,33 @@ where }); } +fn read_path>(path: P) -> io::Result { + let bytes = fs::read(&path)?; + match bytes.as_slice() { + [0xFF, 0xFE, ..] => { + let utf16_data: Vec = bytes[2..] + .chunks(2) + .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) + .collect(); + String::from_utf16(&utf16_data) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-16 LE")) + } + [0xFE, 0xFF, ..] => { + let utf16_data: Vec = bytes[2..] + .chunks(2) + .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]])) + .collect(); + String::from_utf16(&utf16_data) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-16 BE")) + } + [0xEF, 0xBB, 0xBF, ..] => String::from_utf8(bytes[3..].to_vec()) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8 BOM")), + _ => { + String::from_utf8(bytes).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8")) + } + } +} + fn to_absolute(dir: &Path, file: &str) -> Option { let path = Path::new(&file); if path.is_relative() { @@ -969,4 +996,21 @@ users: json!({"audience": "foo", "other": "bar"}) ); } + + #[tokio::test] + async fn parse_kubeconfig_encodings() { + let files = vec![ + "kubeconfig_utf8.yaml", + "kubeconfig_utf16le.yaml", + "kubeconfig_utf16be.yaml", + ]; + + for file_name in files { + let path = PathBuf::from(format!("{}/src/config/test_data/{}", env!("CARGO_MANIFEST_DIR"), file_name)); + let cfg = Kubeconfig::read_from(path).unwrap(); + assert_eq!(cfg.clusters[0].name, "k3d-promstack"); + assert_eq!(cfg.contexts[0].name, "k3d-promstack"); + assert_eq!(cfg.auth_infos[0].name, "admin@k3d-k3s-default"); + } + } } diff --git a/kube-client/src/config/test_data/kubeconfig_utf16be.yaml b/kube-client/src/config/test_data/kubeconfig_utf16be.yaml new file mode 100644 index 000000000..1788dc1cd Binary files /dev/null and b/kube-client/src/config/test_data/kubeconfig_utf16be.yaml differ diff --git a/kube-client/src/config/test_data/kubeconfig_utf16le.yaml b/kube-client/src/config/test_data/kubeconfig_utf16le.yaml new file mode 100644 index 000000000..edcbc6530 Binary files /dev/null and b/kube-client/src/config/test_data/kubeconfig_utf16le.yaml differ diff --git a/kube-client/src/config/test_data/kubeconfig_utf8.yaml b/kube-client/src/config/test_data/kubeconfig_utf8.yaml new file mode 100644 index 000000000..20879b3a4 --- /dev/null +++ b/kube-client/src/config/test_data/kubeconfig_utf8.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: aGVsbG8K + server: https://0.0.0.0:6443 + name: k3d-promstack +contexts: +- context: + cluster: k3d-promstack + user: admin@k3d-promstack + name: k3d-promstack +users: +- name: admin@k3d-k3s-default + user: + client-certificate-data: aGVsbG8K + client-key-data: aGVsbG8K +current-context: k3d-promstack +kind: Config +preferences: {} \ No newline at end of file