LYnLab

블로그취미로그

Python에서 파일의 MIME 타입 판단하기

Python에서 파일의 MIME 타입 판단하기

2018-01-06#프로그래밍

💡 이 글은 작성된지 1년 이상 지났습니다. 정보글의 경우 최신 내용이 아닐 수 있음에 유의해주세요.

어떤 파일의 MIME 타입을 판단하는 방법에는 크게 두 가지가 있습니다.

  • 파일의 확장자를 통해 추정하기
    확장자를 통해 파일의 타입을 대략적으로 추정할 수 있습니다. .png면 이미지(image/png), .json이면 JSON 파일(application/json) 등으로 비교적 직관적인 추정이 가능합니다.
    하지만 모든 파일이 확장자를 가지고 있다고 보장되지는 않습니다. 또한 확장자는 아무나 간단하게 변경할 수 있기 때문에 각종 공격에 아주 취약합니다.
  • 파일의 시그니처(Signature)를 통해 추정하기
    모든 파일은 고유한 포맷(= 파일 시그니처)을 가지고 있으며, 특히 첫 몇 바이트는 타입에 따라 고유한 값을 가집니다. 예를 들어 PNG 파일은 항상 89 50 4E 47 0D 0A 1A 0A의 값으로 시작한다는 규칙이 있습니다.
    UNIX 계열 시스템에서는 파일 시그니처와 타입을 매핑해놓은 매직 파일이 있습니다. UNIX 명령어인 file은 매직 파일을 이용하여 특정 파일의 타입을 추론하는 기능을 담당합니다.

Python에서는 위의 두 가지 방법 모두를 사용할 수 있습니다. 첫 번째 방법은 mimetypes 모듈로, 두 번째 방법은 python-magic 라이브러리로 구현이 가능합니다.

mimetypes 모듈 이용하기

mimetypes 모듈은 Python에 기본적으로 내장되어있는 파일의 확장자를 통해 MIME 타입을 추론합니다. 사용 예제는 아래와 같습니다.

>> import mimetypes

>> mimetypes.guess_type('hello.png')
('image/png', None)

>> mimetypes.guess_type('hello.gz')
(None, 'gzip')

응답으로 tuple이 반환되었습니다.

  • 첫 번째 값은 흔히 Content-Type 헤더에 사용되는 MIME 타입입니다.
  • 두 번째 값은 Content-Encoding 값에 해당하는 인코딩 타입입니다. 위 예제의 gzip처럼 프로그램이 응답을 해독해야할 때 필요한 인코딩을 알려줍니다.

이 두 값은 경우에 따라 모두 None이 될 수 있습니다.

python-magic 라이브러리 이용하기

python-magic은 앞서 언급한 UNIX 명령어 file의 python 래퍼 라이브러리입니다. 아래와 같이 간단한 방법으로 사용하실 수 있습니다.

>>> import magic
>>> mime_type = magic.from_file('/path/file.png', mime=True)
'image/png'

OS X에는 매직 파일을 사용하기 위하여 libmagic 라이브러리를 설치해야 합니다. (Homebrew를 통해 설치할 수 있습니다.)

(TIP) Django와 연계해서 사용하기

Django에서, 업로드 된 파일을 저장하기 전에 MIME 타입을 알아내고 싶다면 아래처럼 시그널을 이용하면 됩니다.

#### models.py
class MyModel(models.Model):
    name = models.CharField(max_length=50, unique=True, null=False)
    file = models.FileField()

@receiver(pre_save, sender=MyModel)
def pre_save_my_model(sender, instance, *args, **kwargs):
    mime_type = magic.from_buffer(instance.file.read(1024), mime=True)
    # Do something with mime_type

예를 들어 업로드 된 파일이 js 파일일 경우 저장을 막고 싶다면, 위 처럼 시그널 리시버에서 mime_type을 검증해 예외를 발생시킨 뒤, 인스턴스를 save()하는 부분에서 이를 catch하여 예외 응답을 반환하는 방식으로 구현할 수 있습니다.

#### view.py
def upload(request):
    obj = MyModel( ... )    
    try:
        obj.save()
    except MyError:
        # Handle exception

관련된 글

Rails와 GitHub Actions에 커버리지 레포트를 달아보자

이 블로그의 CMS이기도 한 Shiori를 대폭 리팩토링하면서 테스트가 얼마나 잘 작성되어있는지 궁금해졌습니다.

Rails Global ID로 전역 객체 식별하기

Global ID는 Rails의 모든 객체를 식별할 수 있는 URI(Uniform Resource Identifier)입니다.

Ruby on WebAssembly: 살짝 맛보기

Ruby 3.2에 추가된 WebAssembly 지원을 간단하게 테스트해봅시다.

작성한 댓글은 giscus를 통해 GitHub Discussion에 저장됩니다.

크리에이티브 커먼즈 라이선스크리에이티브 커먼즈 저작자표시크리에이티브 커먼즈 동일조건변경허락

본 사이트의 저작물은 별도의 언급이 없는 한 크리에이티브 커먼즈 저작자표시-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.

© 2011 - 2024 Hoerin Doh, All rights reserved.

LYnLab 로고GitHubTwitterInstagram