CTF/2025 CCE Photo Editing

[CCE] utils.py

cucu0417 2025. 9. 10. 14:41

오늘은 utils.py 코드 해석을 해보겠다

 

아래 내용은 정말 사소한 개념을 정리해 놓은 것이니 참고용으로 보는게 좋을 것 같다

 

# app/utils.py 
import os
import uuid
import io
import base64
import logging

import os

  • 파일 복사,디렉토리 생성 등

import uuid

  • 고유 ID를 만들기 위한 모듈

import io

  • 텍스트 및 바이너리 데이터의 스트림 처리를 위해 사용하는 모듈

스트림 처리

: 데이터가 생성되는 즉시 처리하여 실시간으로 결과를 얻는 데이터 처리 방식

import base64

  • Base64 인코딩 및 디코딩을 수행할 수 있음

import logging

  • 로그를 출력하거나 파일로 남기는 작업 수행

 

from flask import current_app
from werkzeug.utils import secure_filename
from PIL import Image, ImageMath

from flask import current_app

  • flask
  • 웹 개발, 배포를 할 수 있는 도구들을 최소한의 크기로 줄인 웹 프레임워크
  • current_app
  • 현재 요청을 처리하는 애플리케이션 인스턴스에 접근할 때 사용

프레임 워크란?

반복적인 작업을 줄이기 위한 뼈대와 도구의 집합

from 모듈명 import 객체명

  • 특정 모듈에서 함수,변수 등 원하는 객체만 선택적으로 가져와 사용

from werkzeug.utils import secure_filename

  • werkzeug
  • HTTP 요청과 응답을 처리하는데 사용하는 핵심 라이브러리
  • secure_filename
  • 업로드된 파일을 안전하게 보호하기 위한 필수 함수
  • ( 위험한 문자 제거, 디렉토리 탐색 방지, 안전한 파일 이름으로 변경 )

프레임워크 vs 라이브러리

프레임워크 : 사용자의 코드를 실행 (틀)

라이브러리 : 사용자의 코드 안에서 실행되는 것 (요소)

 

def allowed_file(filename):
    if not filename or '.' not in filename:
        return False
    ext = filename.rsplit('.', 1)[1].lower()
    return ext in ALLOWED_EXTENSIONS

ext = filename.rsplit('.', 1)[1].lower()

  • rsplit(구분자,분할할 최대 횟수)
  • 오른쪽에서부터 특정 구분자를 기준으로 분할하여 리스트로 반환
  • lower()
  • 모든 문자들을 소문자로 바꿈

 

def validate_file(file_storage):
    if not file_storage or not file_storage.filename:
        return False, "파일이 선택되지 않았습니다."
    filename = secure_filename(file_storage.filename)
    if not filename:
        return False, "유효하지 않은 파일명입니다."

    if '.' in filename:
        ext = filename.rsplit('.', 1)[1].lower()
        if ext not in ALLOWED_EXTENSIONS:
            return False, f"허용되지 않는 확장자입니다. ({', '.join(ALLOWED_EXTENSIONS)}만 허용)"

secure_filename(file_storage.filename)

  • 파일 보안을 위한 함수

{', '.join(ALLOWED_EXTENSIONS)}

  • ‘구분자’.join(리스트)
  • 리스트의 요소를 연결할 때 그 사이에 구분자를 삽입
  • {} (중괄호)
  • 변수를 나타낼 때, 집합이나 딕셔너리 자료형을 정의할 때 사용

 

try:
    file_storage.seek(0)  
    file_data = file_storage.read()
    file_size = len(file_data)
    file_storage.seek(0)
        
    if file_size > MAX_FILE_SIZE:
    	return False, "파일 크기가 너무 큽니다. (최대 10MB)"
        
    if file_size == 0:
        return False, "빈 파일은 업로드할 수 없습니다."
            
    except Exception as e:
        return False, f"파일 검증 중 오류가 발생했습니다: {str(e)}"
    
    return True, "유효한 파일입니다."

file_storage.seek(0)

  • seek(0)
  • 파일을 여러번 읽기 위해 커서를 맨 앞으로 이동시킴

seek 함수란?

더보기
더보기
더보기
더보기

seek(offset,기준점)

  • offset
  • 기준점으로부터의 앞뒤바이트 수를 나타냄 (양수는 뒷쪽, 음수면 앞쪽으로 이동)
  • 기준점
  • 0 → 파일의 맨 앞으로 이동 ( 읽기 : 처음부터 읽기 , 쓰기 : 앞쪽에 쓰기 )
  • 1 → 현재의 위치 ( 읽기 : 거의 사용 X , 쓰기 : 문장을 새로 삽입 )
  • 2 → 파일의 맨 끝으로 이동 ( 읽기 : 더 이상 읽을 것이 없음, 쓰기 : 이어쓰기 )

except Exception as e:

  • 미리 정의된 예외를 제외한 에러 처리
  • except 예외 as 변수
  • 발생한 예외의 에러 메시지를 받아올 수 있음
  • Exception
  • 모든 예외의 부모 클래스

 

def get_user_upload_folder(user_uuid):
    """사용자별 업로드 폴더 경로 반환"""
    base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    user_folder = os.path.join(base_dir, 'static', 'uploads', 'board', user_uuid)
    os.makedirs(user_folder, exist_ok=True)
    return user_folder

os.path

  • 파일 경로를 다루는 함수들 제공

os.path.dirname(path)

  • 주어진 경로(path)에서 파일명이나 마지막 디렉토리를 제외한 상위 디렉토리 경로만 반환

os.path.abspath(path)

  • 상대 경로를 절대 경로로 변경
  • (전체 경로를 알 수 있음)

__file__

  • 현재 실행 중인 파일의 절대 혹은 상대 경로를 담고 있는 변수

os.path.join

  • 운영체제에 맞는 경로 구분자(ex. 윈도우 ‘\’)를 사용하여 여러 문자열을 하나의 경로로 연결

os.makedirs(user_folder, exist_ok=True)

  • os.makedirs
  • 여러 파일을 동시에 만들 수 있음
  • ex) os.mkdir(”경로/폴더1/폴더2/폴더3”)
  • exist_ok
  • 입력한 폴더들이 다 존재하는 경우에 오류를 발생하는가
  • True : 입력한 모든 경로의 모든 폴더가 존재더라도 오류
  • False : 입력한 모든 경로의 모든 폴더가 존재하는 경우 FileExistError 발생

 

def save_file_for_user(file_storage, user_uuid):
    is_valid, message = validate_file(file_storage)
    filename = file_storage.filename
    if not is_valid:
        raise ValueError(message)

is_valid, message = validate_file(file_storage)

  • 전에 미리 정의한 validate_file함수는 boolean과 에러 메시지를 반환
  • (ex. (True, “유효한 파일입니다.”)라는 튜플)
  • is_vaild에는 boolean, message에는 에러 메시지가 들어감

if not is_valid:

  • if not
  • 주어진 조건이 거짓일 때 (True가 아닐 때) 특정 코드를 실행

raise ValueError(message)

  • raise Exception(”예외 메시지”)
  • Exception : 발생시킬 예외의 종류 ( 일반적으로 오류 원인을 나타내는 문자열 메시지 )

 

if '.' in filename:
    ext = filename.rsplit('.', 1)[1].lower()
    filename = f"{uuid.uuid4().hex}.{ext}"
    
user_folder = get_user_upload_folder(user_uuid)
file_path = os.path.join(user_folder, filename)
    
try:
    file_storage.save(file_path)
    if not os.path.exists(file_path) or os.path.getsize(file_path) == 0:
        raise ValueError("파일이 올바르게 저장되지 않았습니다.")
            
except Exception as e:
    if os.path.exists(file_path):
    	os.remove(file_path)
    raise ValueError(f"파일 저장 실패: {str(e)}")
    
return filename
def delete_user_file(filename, user_uuid):
    if not filename:
        return 
    try:
        user_folder = get_user_upload_folder(user_uuid)
        file_path = os.path.join(user_folder, filename)
        if os.path.exists(file_path):
            os.remove(file_path)
    except Exception:
        pass

except Exception: pass

  • 예외가 발생하였을 때 처리하지 않고 넘어감

 

def save_pil_image_for_user(pil_image, user_uuid, original_filename=None):

    user_folder = get_user_upload_folder(user_uuid)
    ext = 'png'
    if pil_image.mode == 'I':
        ext = 'png'
    elif ext in ['jpg', 'jpeg'] and pil_image.mode != 'RGB':
        pil_image = pil_image.convert('RGB')
    
    unique_filename = f"{uuid.uuid4().hex}.{ext}"
    file_path = os.path.join(user_folder, unique_filename)

    try:
        pil_image.save(file_path)
    except Exception as e:
        raise ValueError(f"PIL Image 저장 실패: {str(e)}")
    
    return unique_filename

if pil_image.mode == ‘I’;

  • Pillow 라이브러리에서 이미지의 모드가 32비트 픽셀 형식인지 확인

image.mode

  • Pillow 라이브러리에서 이미지의 색상 모드를 나타내는 문자열 (이미지의 픽셀 유형 및 깊이)

pil_image=pil_image.convert(’RGB’)

  • 이미지 모드를 RGB로 변환

pil_image.save(file_path)

  • 이미지를 파일에 저장

 

def get_user_file_url(filename, user_uuid):
    if not filename:
        return None
    return f"/board/uploads/{user_uuid}/{filename}"
def get_processed_images(filenames, user_uuid):
    upload_folder = get_user_upload_folder(user_uuid)
    images = []
    for fname in filenames:
        file_path = os.path.join(upload_folder, fname)
        try:
            img = Image.open(file_path).convert('RGB')
            images.append(img)
        except IOError as e:
            logging.error(f"Error opening image file {file_path}: {e}")
            continue

    if not images:
        return []

    return images

for fname in filenames:

  • for 변수 in 시퀀스(반복대상):

images.append(img)

  • images 리스트에 img를 추가

'CTF > 2025 CCE Photo Editing' 카테고리의 다른 글

[CCE] image_processor.py  (0) 2025.09.11