File size: 2,919 Bytes
df4a21a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Security utilities for the application.
"""

import hashlib
import secrets
from typing import List, Optional

from app.core.logging import get_logger

logger = get_logger(__name__)

# Maximum allowed file size (10 MB)
MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024

# Allowed image MIME types
ALLOWED_MIME_TYPES = [
    "image/jpeg",
    "image/png",
    "image/gif",
    "image/webp",
    "image/bmp"
]

# Image magic bytes for validation
IMAGE_SIGNATURES = {
    b'\xff\xd8\xff': 'image/jpeg',
    b'\x89PNG\r\n\x1a\n': 'image/png',
    b'GIF87a': 'image/gif',
    b'GIF89a': 'image/gif',
    b'RIFF': 'image/webp',  # WebP (partial)
    b'BM': 'image/bmp'
}


def validate_file_size(content: bytes, max_size: int = MAX_FILE_SIZE_BYTES) -> bool:
    """
    Validate that file size is within allowed limits.
    
    Args:
        content: File content as bytes
        max_size: Maximum allowed size in bytes
        
    Returns:
        True if valid, False otherwise
    """
    return len(content) <= max_size


def detect_mime_type(content: bytes) -> Optional[str]:
    """
    Detect MIME type from file content using magic bytes.
    
    Args:
        content: File content as bytes
        
    Returns:
        Detected MIME type or None
    """
    for signature, mime_type in IMAGE_SIGNATURES.items():
        if content.startswith(signature):
            return mime_type
    return None


def validate_image_content(
    content: bytes,
    allowed_types: List[str] = ALLOWED_MIME_TYPES
) -> bool:
    """
    Validate image content by checking magic bytes.
    
    Args:
        content: File content as bytes
        allowed_types: List of allowed MIME types
        
    Returns:
        True if valid image type, False otherwise
    """
    detected_type = detect_mime_type(content)
    if detected_type is None:
        return False
    return detected_type in allowed_types


def compute_file_hash(content: bytes, algorithm: str = "sha256") -> str:
    """
    Compute hash of file content.
    
    Args:
        content: File content as bytes
        algorithm: Hash algorithm (sha256, md5, etc.)
        
    Returns:
        Hex-encoded hash string
    """
    hasher = hashlib.new(algorithm)
    hasher.update(content)
    return hasher.hexdigest()


def generate_request_id() -> str:
    """
    Generate a unique request ID.
    
    Returns:
        Random hex string
    """
    return secrets.token_hex(8)


def sanitize_filename(filename: str) -> str:
    """
    Sanitize a filename to prevent path traversal.
    
    Args:
        filename: Original filename
        
    Returns:
        Sanitized filename
    """
    # Remove path separators and null bytes
    sanitized = filename.replace("/", "_").replace("\\", "_").replace("\x00", "")
    # Remove leading dots to prevent hidden files
    sanitized = sanitized.lstrip(".")
    return sanitized[:255] if sanitized else "unnamed"