Spaces:
Running
Running
| """ | |
| Utility functions for DFIR iOS Enhancement Application | |
| """ | |
| from datetime import datetime, timedelta, timezone | |
| from pathlib import Path | |
| from typing import Any, Optional | |
| import hashlib | |
| import sqlite3 | |
| # Apple epoch (January 1, 2001) | |
| APPLE_EPOCH = datetime(2001, 1, 1, tzinfo=timezone.utc) | |
| def apple_time_to_datetime(ts: Any) -> Optional[datetime]: | |
| """ | |
| Convert Apple timestamp to Python datetime. | |
| Apple uses a custom epoch starting from January 1, 2001. | |
| Args: | |
| ts: Apple timestamp (can be seconds or nanoseconds) | |
| Returns: | |
| datetime object or None if conversion fails | |
| """ | |
| if ts is None: | |
| return None | |
| try: | |
| ts = float(ts) | |
| # Handle nanoseconds | |
| if ts > 1e12: | |
| ts = ts / 1e9 | |
| return APPLE_EPOCH + timedelta(seconds=ts) | |
| except Exception: | |
| return None | |
| def normalize_phone(phone: Optional[str]) -> Optional[str]: | |
| """ | |
| Normalize phone number by removing common formatting characters. | |
| Args: | |
| phone: Raw phone number string | |
| Returns: | |
| Normalized phone number or None | |
| """ | |
| if not phone: | |
| return phone | |
| value = str(phone).replace("+1", "").replace(" ", "").replace("-", "") | |
| value = value.replace("(", "").replace(")", "") | |
| return value.strip() or None | |
| def sha256_file(path: Path, chunk_size: int = 1024 * 1024) -> str: | |
| """ | |
| Calculate SHA256 hash of a file. | |
| Args: | |
| path: Path to the file | |
| chunk_size: Size of chunks to read (default 1MB) | |
| Returns: | |
| Hexadecimal SHA256 hash string | |
| """ | |
| h = hashlib.sha256() | |
| with path.open("rb") as f: | |
| while True: | |
| chunk = f.read(chunk_size) | |
| if not chunk: | |
| break | |
| h.update(chunk) | |
| return h.hexdigest() | |
| def get_manifest_files(manifest_db: Path) -> list: | |
| """ | |
| Query Manifest.db for file entries. | |
| Args: | |
| manifest_db: Path to Manifest.db | |
| Returns: | |
| List of file entries from manifest | |
| """ | |
| if not manifest_db.exists(): | |
| return [] | |
| conn = sqlite3.connect(str(manifest_db)) | |
| conn.row_factory = sqlite3.Row | |
| cursor = conn.cursor() | |
| try: | |
| cursor.execute(""" | |
| SELECT fileID, domain, relativePath, flags, file | |
| FROM Files | |
| WHERE relativePath IS NOT NULL | |
| """) | |
| return [dict(row) for row in cursor.fetchall()] | |
| except Exception: | |
| return [] | |
| finally: | |
| conn.close() | |
| def find_artifact_in_manifest(manifest_files: list, artifact_name: str) -> Optional[dict]: | |
| """ | |
| Find a specific artifact in manifest files. | |
| Args: | |
| manifest_files: List of manifest file entries | |
| artifact_name: Name of artifact to find | |
| Returns: | |
| File entry dict or None | |
| """ | |
| for file_entry in manifest_files: | |
| if artifact_name in (file_entry.get('relativePath') or ''): | |
| return file_entry | |
| return None |