File size: 3,934 Bytes
1e4fc28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Image storage utilities for saving and serving uploaded images.
"""
import os
import uuid
import shutil
from pathlib import Path
from typing import Optional, Tuple
from werkzeug.utils import secure_filename


def ensure_images_dir(images_dir: str) -> str:
    """Ensure images directory exists and return its path."""
    os.makedirs(images_dir, exist_ok=True)
    return images_dir


def generate_unique_filename(original_filename: str) -> str:
    """
    Generate a unique filename to avoid collisions.
    Format: {uuid}_{secure_original_name} or just {uuid}.jpg if original is invalid
    """
    # Get secure base name
    base_name = secure_filename(original_filename)
    if not base_name:
        base_name = "upload.jpg"
    
    # Add UUID prefix for uniqueness (use full UUID to ensure uniqueness)
    name, ext = os.path.splitext(base_name)
    if not ext or ext.lower() not in ('.jpg', '.jpeg', '.png'):
        ext = '.jpg'
    unique_id = str(uuid.uuid4())  # Full UUID for better uniqueness
    return f"{unique_id}_{name}{ext}"


def save_image(source_path: str, images_dir: str, original_filename: str) -> Optional[str]:
    """
    Save an image from source_path to images_dir with a unique filename.
    
    Args:
        source_path: Path to source image file
        images_dir: Directory to save images to
        original_filename: Original filename for reference
    
    Returns:
        Stored filename (relative to images_dir) or None on failure
    """
    try:
        ensure_images_dir(images_dir)
        
        # Generate unique filename
        stored_filename = generate_unique_filename(original_filename)
        dest_path = os.path.join(images_dir, stored_filename)
        
        # Copy file
        shutil.copy2(source_path, dest_path)
        
        return stored_filename
    except Exception as e:
        # Log error but don't fail the request
        import logging
        logging.getLogger(__name__).exception(f"Failed to save image: {e}")
        return None


def get_image_path(images_dir: str, filename: str) -> Optional[str]:
    """
    Get full path to an image file if it exists.
    
    Args:
        images_dir: Base images directory
        filename: Image filename
    
    Returns:
        Full path to image or None if not found
    """
    if not filename:
        return None
    
    # Security: ensure filename doesn't contain path traversal
    # Extract just the basename to prevent directory traversal
    base_filename = os.path.basename(filename)
    safe_filename = secure_filename(base_filename)
    
    if not safe_filename:
        return None
    
    # Use safe_filename for the path (secure_filename may have sanitized it)
    # But also try the original if it's already safe
    image_path = os.path.join(images_dir, safe_filename)
    
    if os.path.exists(image_path) and os.path.isfile(image_path):
        return image_path
    
    # Also try the original filename if it's different and seems safe
    if safe_filename != base_filename:
        # Check if original is safe (no path separators, no parent dir references)
        if base_filename == filename and '/' not in base_filename and '\\' not in base_filename and '..' not in base_filename:
            alt_path = os.path.join(images_dir, base_filename)
            if os.path.exists(alt_path) and os.path.isfile(alt_path):
                return alt_path
    
    return None


def delete_image(images_dir: str, filename: str) -> bool:
    """
    Delete an image file.
    
    Args:
        images_dir: Base images directory
        filename: Image filename to delete
    
    Returns:
        True if deleted, False otherwise
    """
    try:
        image_path = get_image_path(images_dir, filename)
        if image_path and os.path.exists(image_path):
            os.remove(image_path)
            return True
        return False
    except Exception:
        return False