File size: 5,303 Bytes
e092c28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
"""
APK Deep Diff Analyzer - Utilities Module
"""
import hashlib
import os
import tempfile
import shutil
import zipfile
from pathlib import Path
from typing import Dict, List, Tuple, Optional, Any
import time


def get_file_hash(filepath: str, algorithm: str = "sha256", chunk_size: int = 8192) -> str:
    """Calculate file hash using streaming (memory efficient)."""
    h = hashlib.new(algorithm)
    with open(filepath, "rb") as f:
        while chunk := f.read(chunk_size):
            h.update(chunk)
    return h.hexdigest()


def get_file_size_formatted(size_bytes: int) -> str:
    """Format file size in human readable format."""
    if size_bytes < 1024:
        return f"{size_bytes} B"
    elif size_bytes < 1024 * 1024:
        return f"{size_bytes / 1024:.2f} KB"
    elif size_bytes < 1024 * 1024 * 1024:
        return f"{size_bytes / (1024 * 1024):.2f} MB"
    else:
        return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB"


def create_temp_dir(prefix: str = "apk_") -> str:
    """Create a temporary directory."""
    return tempfile.mkdtemp(prefix=prefix)


def cleanup_temp_dir(path: str):
    """Remove temporary directory and all contents."""
    if os.path.exists(path):
        shutil.rmtree(path, ignore_errors=True)


def extract_apk(apk_path: str, extract_dir: str) -> bool:
    """Extract APK (ZIP) contents to directory."""
    try:
        with zipfile.ZipFile(apk_path, 'r') as zf:
            zf.extractall(extract_dir)
        return True
    except Exception:
        return False


def get_apk_info(apk_path: str) -> Dict[str, Any]:
    """Get basic APK file information."""
    info = {
        "filename": os.path.basename(apk_path),
        "size": os.path.getsize(apk_path),
        "size_formatted": get_file_size_formatted(os.path.getsize(apk_path)),
        "sha256": get_file_hash(apk_path, "sha256"),
        "md5": get_file_hash(apk_path, "md5"),
    }
    return info


def list_files_recursive(directory: str) -> List[Tuple[str, int, str]]:
    """
    List all files recursively.
    Returns list of (relative_path, size, sha256) tuples.
    """
    files = []
    base_path = Path(directory)
    for filepath in base_path.rglob("*"):
        if filepath.is_file():
            rel_path = filepath.relative_to(base_path).as_posix()
            size = filepath.stat().st_size
            sha256 = get_file_hash(str(filepath))
            files.append((rel_path, size, sha256))
    return files


def get_zip_entries(apk_path: str) -> List[Dict[str, Any]]:
    """Get ZIP entry information from APK."""
    entries = []
    try:
        with zipfile.ZipFile(apk_path, 'r') as zf:
            for info in zf.infolist():
                entries.append({
                    "filename": info.filename,
                    "size": info.file_size,
                    "compressed_size": info.compress_size,
                    "compression": info.compress_type,
                    "crc": info.CRC,
                })
    except Exception:
        pass
    return entries


def calculate_similarity(size1: int, size2: int, hash1: str, hash2: str) -> float:
    """Calculate a simple similarity score between two files."""
    if hash1 == hash2:
        return 100.0
    if size1 == 0 and size2 == 0:
        return 100.0
    if size1 == 0 or size2 == 0:
        return 0.0
    size_diff = abs(size1 - size2) / max(size1, size2)
    similarity = max(0.0, (1.0 - size_diff) * 100)
    return round(similarity, 2)


def format_time_elapsed(seconds: float) -> str:
    """Format elapsed time."""
    if seconds < 60:
        return f"{seconds:.2f} ثانية"
    elif seconds < 3600:
        return f"{seconds / 60:.2f} دقيقة"
    else:
        return f"{seconds / 3600:.2f} ساعة"


class ProgressTracker:
    """Track analysis progress."""

    STAGES = [
        "Uploading",
        "Validating APK",
        "Extracting APK",
        "Analyzing ZIP structure",
        "Analyzing AndroidManifest.xml",
        "Comparing resources",
        "Comparing DEX files",
        "Comparing native libraries",
        "Comparing assets",
        "Comparing signatures",
        "Calculating hashes",
        "Generating report",
    ]

    def __init__(self):
        self.current_stage = 0
        self.start_time = time.time()
        self.stage_times = {}

    def set_stage(self, stage_index: int):
        """Set current stage."""
        if 0 <= stage_index < len(self.STAGES):
            self.current_stage = stage_index
            self.stage_times[stage_index] = time.time()

    def get_progress(self) -> Tuple[float, str, str]:
        """Get progress percentage, current stage name, and time elapsed."""
        total = len(self.STAGES)
        pct = (self.current_stage / total) * 100
        stage_name = self.STAGES[self.current_stage] if self.current_stage < total else "Complete"
        elapsed = format_time_elapsed(time.time() - self.start_time)
        return pct, stage_name, elapsed

    def get_stage_time(self, stage_index: int) -> str:
        """Get time spent on a specific stage."""
        if stage_index in self.stage_times and stage_index + 1 in self.stage_times:
            elapsed = self.stage_times[stage_index + 1] - self.stage_times[stage_index]
            return format_time_elapsed(elapsed)
        return "N/A"