mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-12 22:26:13 -04:00

Fixed AMI PFAT Extract file naming of "ALL" and "OOB" Upgraded dependency "dissect.util" from 3.18 to 3.19
286 lines
7.4 KiB
Python
286 lines
7.4 KiB
Python
#!/usr/bin/env python3 -B
|
|
# coding=utf-8
|
|
|
|
"""
|
|
Copyright (C) 2022-2025 Plato Mavropoulos
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import shutil
|
|
import stat
|
|
import sys
|
|
|
|
from pathlib import Path, PurePath
|
|
from typing import Callable, Final
|
|
|
|
from biosutilities.common.system import system_platform
|
|
from biosutilities.common.texts import to_string
|
|
|
|
MAX_WIN_COMP_LEN: Final[int] = 255
|
|
|
|
|
|
def safe_name(in_name: str) -> str:
|
|
"""
|
|
Fix illegal/reserved Windows characters
|
|
Can also be used to nuke dangerous paths
|
|
"""
|
|
|
|
name_repr: str = repr(in_name).strip("'")
|
|
|
|
return re.sub(r'[\\/:"*?<>|]+', '_', name_repr)
|
|
|
|
|
|
def safe_path(base_path: str, user_paths: str | list | tuple) -> str:
|
|
""" Check and attempt to fix illegal/unsafe OS path traversals """
|
|
|
|
# Convert base path to absolute path
|
|
base_path = real_path(in_path=base_path)
|
|
|
|
# Merge user path(s) to string with OS separators
|
|
user_path: str = to_string(in_object=user_paths, sep_char=os.sep)
|
|
|
|
# Create target path from base + requested user path
|
|
target_path: str = norm_path(base_path=base_path, user_path=user_path)
|
|
|
|
# Check if target path is OS illegal/unsafe
|
|
if is_safe_path(base_path=base_path, target_path=target_path):
|
|
return target_path
|
|
|
|
# Re-create target path from base + leveled/safe illegal "path" (now file)
|
|
nuked_path: str = norm_path(base_path=base_path, user_path=safe_name(in_name=user_path))
|
|
|
|
# Check if illegal path leveling worked
|
|
if is_safe_path(base_path=base_path, target_path=nuked_path):
|
|
return nuked_path
|
|
|
|
# Still illegal, raise exception to halt execution
|
|
raise OSError(f'Encountered illegal path traversal: {user_path}')
|
|
|
|
|
|
def is_safe_path(base_path: str, target_path: str) -> bool:
|
|
""" Check for illegal/unsafe OS path traversal """
|
|
|
|
base_path = real_path(in_path=base_path)
|
|
|
|
target_path = real_path(in_path=target_path)
|
|
|
|
common_path: str = os.path.commonpath((base_path, target_path))
|
|
|
|
return base_path == common_path
|
|
|
|
|
|
def norm_path(base_path: str, user_path: str) -> str:
|
|
""" Create normalized base path + OS separator + user path """
|
|
|
|
return os.path.normpath(base_path + os.sep + user_path)
|
|
|
|
|
|
def real_path(in_path: str) -> str:
|
|
""" Get absolute path, resolving any symlinks """
|
|
|
|
return os.path.realpath(in_path)
|
|
|
|
|
|
def agnostic_path(in_path: str) -> PurePath:
|
|
""" Get Windows/Posix OS-agnostic path """
|
|
|
|
return PurePath(in_path.replace('\\', os.sep))
|
|
|
|
|
|
def path_parent(in_path: str) -> Path:
|
|
""" Get absolute parent of path """
|
|
|
|
return Path(in_path).parent.absolute()
|
|
|
|
|
|
def path_name(in_path: str, limit: bool = False) -> str:
|
|
""" Get final path component, with suffix """
|
|
|
|
comp_name: str = PurePath(in_path).name
|
|
|
|
if limit and system_platform()[1]:
|
|
comp_name = comp_name[:MAX_WIN_COMP_LEN - len(extract_suffix())]
|
|
|
|
return comp_name
|
|
|
|
|
|
def path_stem(in_path: str) -> str:
|
|
""" Get final path component, w/o suffix """
|
|
|
|
return PurePath(in_path).stem
|
|
|
|
|
|
def path_size(in_path: str) -> int:
|
|
""" Get path size (bytes) """
|
|
|
|
return os.stat(in_path).st_size
|
|
|
|
|
|
def path_suffixes(in_path: str) -> list[str]:
|
|
""" Get list of path file extensions """
|
|
|
|
return PurePath(in_path).suffixes or ['']
|
|
|
|
|
|
def make_dirs(in_path: str, parents: bool = True, exist_ok: bool = True, delete: bool = False):
|
|
""" Create folder(s), controlling parents, existence and prior deletion """
|
|
|
|
if delete:
|
|
delete_dirs(in_path=in_path)
|
|
|
|
Path.mkdir(Path(in_path), parents=parents, exist_ok=exist_ok)
|
|
|
|
|
|
def delete_dirs(in_path: str) -> None:
|
|
""" Delete folder(s), if present """
|
|
|
|
if is_dir(in_path=in_path):
|
|
shutil.rmtree(in_path, onerror=clear_readonly_callback) # pylint: disable=deprecated-argument
|
|
|
|
|
|
def delete_file(in_path: str) -> None:
|
|
""" Delete file, if present """
|
|
|
|
if is_file(in_path=in_path):
|
|
clear_readonly(in_path=in_path)
|
|
|
|
os.remove(in_path)
|
|
|
|
|
|
def rename_file(in_path: str, in_dest: str) -> None:
|
|
""" Rename file with path or name destination, if present """
|
|
|
|
if is_file(in_path=in_path):
|
|
clear_readonly(in_path=in_path)
|
|
|
|
if is_file(in_path=in_dest, allow_broken_links=True):
|
|
clear_readonly(in_path=in_dest)
|
|
|
|
out_path: str = in_dest
|
|
else:
|
|
out_path = os.path.join(path_parent(in_path=in_path), in_dest)
|
|
|
|
os.replace(in_path, out_path)
|
|
|
|
|
|
def copy_file(in_path: str, out_path: str, metadata: bool = False) -> None:
|
|
""" Copy file to path with or w/o metadata """
|
|
|
|
if metadata:
|
|
shutil.copy2(in_path, out_path)
|
|
else:
|
|
shutil.copy(in_path, out_path)
|
|
|
|
|
|
def clear_readonly(in_path: str) -> None:
|
|
""" Clear read-only file attribute """
|
|
|
|
os.chmod(in_path, stat.S_IWRITE)
|
|
|
|
|
|
def clear_readonly_callback(in_func: Callable, in_path: str, _) -> None:
|
|
""" Clear read-only file attribute (on shutil.rmtree error) """
|
|
|
|
clear_readonly(in_path=in_path)
|
|
|
|
in_func(in_path)
|
|
|
|
|
|
def path_files(in_path: str, follow_links: bool = False, root_only: bool = False) -> list[str]:
|
|
""" Walk path to get all files """
|
|
|
|
file_paths: list[str] = []
|
|
|
|
for root_path, _, file_names in os.walk(in_path, followlinks=follow_links):
|
|
for file_name in file_names:
|
|
file_path: str = os.path.abspath(os.path.join(root_path, file_name))
|
|
|
|
if is_file(in_path=file_path):
|
|
file_paths.append(file_path)
|
|
|
|
if root_only:
|
|
break
|
|
|
|
return file_paths
|
|
|
|
|
|
def is_dir(in_path: str) -> bool:
|
|
""" Check if path is a directory """
|
|
|
|
return Path(in_path).is_dir()
|
|
|
|
|
|
def is_file(in_path: str, allow_broken_links: bool = False) -> bool:
|
|
""" Check if path is a regural file or symlink (valid or broken) """
|
|
|
|
in_path_abs: str = os.path.abspath(in_path)
|
|
|
|
if os.path.lexists(in_path_abs):
|
|
if not is_dir(in_path=in_path_abs):
|
|
if allow_broken_links:
|
|
return os.path.isfile(in_path_abs) or os.path.islink(in_path_abs)
|
|
|
|
return os.path.isfile(in_path_abs)
|
|
|
|
return False
|
|
|
|
|
|
def is_access(in_path: str, access_mode: int = os.R_OK, follow_links: bool = False) -> bool:
|
|
""" Check if path is accessible """
|
|
|
|
if not follow_links and os.access not in os.supports_follow_symlinks:
|
|
follow_links = True
|
|
|
|
return os.access(in_path, access_mode, follow_symlinks=follow_links)
|
|
|
|
|
|
def is_file_read(in_path: str) -> bool:
|
|
""" Check if path is a readable file """
|
|
|
|
return isinstance(in_path, str) and is_file(in_path=in_path) and is_access(in_path=in_path)
|
|
|
|
|
|
def is_dir_read(in_path: str) -> bool:
|
|
""" Check if path is a readable directory """
|
|
|
|
return isinstance(in_path, str) and is_dir(in_path=in_path) and is_access(in_path=in_path)
|
|
|
|
|
|
def is_empty_dir(in_path: str, follow_links: bool = False) -> bool:
|
|
""" Check if directory is empty (file-wise) """
|
|
|
|
for _, _, filenames in os.walk(in_path, followlinks=follow_links):
|
|
if filenames:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def extract_suffix() -> str:
|
|
""" Set utility extraction stem """
|
|
|
|
return '_extracted'
|
|
|
|
|
|
def extract_folder(in_path: str, suffix: str = extract_suffix()) -> str:
|
|
""" Get utility extraction directory """
|
|
|
|
return f'{in_path}{suffix}'
|
|
|
|
|
|
def project_root() -> str:
|
|
""" Get project root directory """
|
|
|
|
return real_path(in_path=str(Path(__file__).parent.parent))
|
|
|
|
|
|
def runtime_root() -> str:
|
|
""" Get runtime root directory """
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
root: str = str(Path(sys.executable).parent)
|
|
else:
|
|
root = project_root()
|
|
|
|
return real_path(in_path=root)
|