use std::fs;
use std::path::{Path, PathBuf};
use crate::{Error, Result};
const TDATA_MAGIC: [u8; 4] = [0x54, 0x44, 0x46, 0x24];
#[derive(Debug)]
pub struct FileDescriptor {
pub version: u32,
pub data: Vec<u8>,
}
pub fn read_file(name: &str, base_path: &Path) -> Result<FileDescriptor> {
let path = base_path.join(name);
let path_s = base_path.join(format!("{name}s"));
tracing::debug!("Trying to read file: {:?}", path);
let file_data = if path.is_file() {
tracing::debug!("Reading main file: {:?}", path);
fs::read(&path)?
} else if path_s.is_file() {
tracing::debug!("Reading backup file: {:?}", path_s);
fs::read(&path_s)?
} else {
return Err(Error::FileNotFound { file: name.to_owned(), folder: base_path.to_path_buf() });
};
tracing::debug!("Read {} bytes", file_data.len());
parse_file_descriptor(&file_data)
}
fn parse_file_descriptor(data: &[u8]) -> Result<FileDescriptor> {
if data.len() < 8 + 16 {
return Err(Error::invalid_format("file too short"));
}
if data[0..4] != TDATA_MAGIC {
return Err(Error::invalid_format("invalid file magic"));
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
let data_size = data.len() - 8 - 16;
let payload = &data[8..8 + data_size];
let file_md5 = &data[data.len() - 16..];
use md5::{Digest, Md5};
let mut hasher = Md5::new();
hasher.update(payload);
hasher.update((data_size as u32).to_le_bytes());
hasher.update(version.to_le_bytes());
hasher.update(TDATA_MAGIC);
let computed_md5: [u8; 16] = hasher.finalize().into();
tracing::debug!("MD5 check: file={:02x?}, computed={:02x?}", file_md5, computed_md5);
if file_md5 != computed_md5.as_slice() {
return Err(Error::ChecksumMismatch);
}
Ok(FileDescriptor { version, data: payload.to_vec() })
}
pub fn get_absolute_path(path: &str) -> PathBuf {
if let Some(rest) = path.strip_prefix("~/")
&& let Some(home) = dirs::home_dir()
{
return home.join(rest);
}
if path == "~"
&& let Some(home) = dirs::home_dir()
{
return home;
}
PathBuf::from(path)
}
pub fn get_default_tdata_path() -> Option<PathBuf> {
#[cfg(target_os = "linux")]
{
dirs::home_dir().map(|h| h.join(".local/share/TelegramDesktop/tdata"))
}
#[cfg(target_os = "macos")]
{
dirs::home_dir().map(|h| h.join("Library/Application Support/Telegram Desktop/tdata"))
}
#[cfg(target_os = "windows")]
{
dirs::data_local_dir().map(|d| d.join("Telegram Desktop/tdata"))
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
None
}
}