|
|
@@ -0,0 +1,390 @@ |
|
|
|
# coding: utf-8 |
|
|
|
|
|
|
|
# from: https://github.com/file/file/raw/master/python/magic.py |
|
|
|
|
|
|
|
# LICENSE: |
|
|
|
# Copyright (c) Ian F. Darwin 1986-1995. |
|
|
|
# Software written by Ian F. Darwin and others; |
|
|
|
# maintained 1995-present by Christos Zoulas and others. |
|
|
|
# |
|
|
|
# Redistribution and use in source and binary forms, with or without |
|
|
|
# modification, are permitted provided that the following conditions |
|
|
|
# are met: |
|
|
|
# 1. Redistributions of source code must retain the above copyright |
|
|
|
# notice immediately at the beginning of the file, without modification, |
|
|
|
# this list of conditions, and the following disclaimer. |
|
|
|
# 2. Redistributions in binary form must reproduce the above copyright |
|
|
|
# notice, this list of conditions and the following disclaimer in the |
|
|
|
# documentation and/or other materials provided with the distribution. |
|
|
|
# |
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
|
|
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR |
|
|
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
|
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|
|
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
|
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|
|
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
|
|
# SUCH DAMAGE. |
|
|
|
|
|
|
|
''' |
|
|
|
Python bindings for libmagic |
|
|
|
''' |
|
|
|
|
|
|
|
import ctypes |
|
|
|
import threading |
|
|
|
|
|
|
|
from collections import namedtuple |
|
|
|
|
|
|
|
from ctypes import * |
|
|
|
from ctypes.util import find_library |
|
|
|
|
|
|
|
|
|
|
|
def _init(): |
|
|
|
""" |
|
|
|
Loads the shared library through ctypes and returns a library |
|
|
|
L{ctypes.CDLL} instance |
|
|
|
""" |
|
|
|
return ctypes.cdll.LoadLibrary(find_library('magic')) |
|
|
|
|
|
|
|
_libraries = {} |
|
|
|
_libraries['magic'] = _init() |
|
|
|
|
|
|
|
# Flag constants for open and setflags |
|
|
|
MAGIC_NONE = NONE = 0 |
|
|
|
MAGIC_DEBUG = DEBUG = 1 |
|
|
|
MAGIC_SYMLINK = SYMLINK = 2 |
|
|
|
MAGIC_COMPRESS = COMPRESS = 4 |
|
|
|
MAGIC_DEVICES = DEVICES = 8 |
|
|
|
MAGIC_MIME_TYPE = MIME_TYPE = 16 |
|
|
|
MAGIC_CONTINUE = CONTINUE = 32 |
|
|
|
MAGIC_CHECK = CHECK = 64 |
|
|
|
MAGIC_PRESERVE_ATIME = PRESERVE_ATIME = 128 |
|
|
|
MAGIC_RAW = RAW = 256 |
|
|
|
MAGIC_ERROR = ERROR = 512 |
|
|
|
MAGIC_MIME_ENCODING = MIME_ENCODING = 1024 |
|
|
|
MAGIC_MIME = MIME = 1040 # MIME_TYPE + MIME_ENCODING |
|
|
|
MAGIC_APPLE = APPLE = 2048 |
|
|
|
|
|
|
|
MAGIC_NO_CHECK_COMPRESS = NO_CHECK_COMPRESS = 4096 |
|
|
|
MAGIC_NO_CHECK_TAR = NO_CHECK_TAR = 8192 |
|
|
|
MAGIC_NO_CHECK_SOFT = NO_CHECK_SOFT = 16384 |
|
|
|
MAGIC_NO_CHECK_APPTYPE = NO_CHECK_APPTYPE = 32768 |
|
|
|
MAGIC_NO_CHECK_ELF = NO_CHECK_ELF = 65536 |
|
|
|
MAGIC_NO_CHECK_TEXT = NO_CHECK_TEXT = 131072 |
|
|
|
MAGIC_NO_CHECK_CDF = NO_CHECK_CDF = 262144 |
|
|
|
MAGIC_NO_CHECK_TOKENS = NO_CHECK_TOKENS = 1048576 |
|
|
|
MAGIC_NO_CHECK_ENCODING = NO_CHECK_ENCODING = 2097152 |
|
|
|
|
|
|
|
MAGIC_NO_CHECK_BUILTIN = NO_CHECK_BUILTIN = 4173824 |
|
|
|
|
|
|
|
MAGIC_PARAM_INDIR_MAX = PARAM_INDIR_MAX = 0 |
|
|
|
MAGIC_PARAM_NAME_MAX = PARAM_NAME_MAX = 1 |
|
|
|
MAGIC_PARAM_ELF_PHNUM_MAX = PARAM_ELF_PHNUM_MAX = 2 |
|
|
|
MAGIC_PARAM_ELF_SHNUM_MAX = PARAM_ELF_SHNUM_MAX = 3 |
|
|
|
MAGIC_PARAM_ELF_NOTES_MAX = PARAM_ELF_NOTES_MAX = 4 |
|
|
|
MAGIC_PARAM_REGEX_MAX = PARAM_REGEX_MAX = 5 |
|
|
|
MAGIC_PARAM_BYTES_MAX = PARAM_BYTES_MAX = 6 |
|
|
|
|
|
|
|
FileMagic = namedtuple('FileMagic', ('mime_type', 'encoding', 'name')) |
|
|
|
|
|
|
|
|
|
|
|
class magic_set(Structure): |
|
|
|
pass |
|
|
|
magic_set._fields_ = [] |
|
|
|
magic_t = POINTER(magic_set) |
|
|
|
|
|
|
|
_open = _libraries['magic'].magic_open |
|
|
|
_open.restype = magic_t |
|
|
|
_open.argtypes = [c_int] |
|
|
|
|
|
|
|
_close = _libraries['magic'].magic_close |
|
|
|
_close.restype = None |
|
|
|
_close.argtypes = [magic_t] |
|
|
|
|
|
|
|
_file = _libraries['magic'].magic_file |
|
|
|
_file.restype = c_char_p |
|
|
|
_file.argtypes = [magic_t, c_char_p] |
|
|
|
|
|
|
|
_descriptor = _libraries['magic'].magic_descriptor |
|
|
|
_descriptor.restype = c_char_p |
|
|
|
_descriptor.argtypes = [magic_t, c_int] |
|
|
|
|
|
|
|
_buffer = _libraries['magic'].magic_buffer |
|
|
|
_buffer.restype = c_char_p |
|
|
|
_buffer.argtypes = [magic_t, c_void_p, c_size_t] |
|
|
|
|
|
|
|
_error = _libraries['magic'].magic_error |
|
|
|
_error.restype = c_char_p |
|
|
|
_error.argtypes = [magic_t] |
|
|
|
|
|
|
|
_setflags = _libraries['magic'].magic_setflags |
|
|
|
_setflags.restype = c_int |
|
|
|
_setflags.argtypes = [magic_t, c_int] |
|
|
|
|
|
|
|
_load = _libraries['magic'].magic_load |
|
|
|
_load.restype = c_int |
|
|
|
_load.argtypes = [magic_t, c_char_p] |
|
|
|
|
|
|
|
_compile = _libraries['magic'].magic_compile |
|
|
|
_compile.restype = c_int |
|
|
|
_compile.argtypes = [magic_t, c_char_p] |
|
|
|
|
|
|
|
_check = _libraries['magic'].magic_check |
|
|
|
_check.restype = c_int |
|
|
|
_check.argtypes = [magic_t, c_char_p] |
|
|
|
|
|
|
|
_list = _libraries['magic'].magic_list |
|
|
|
_list.restype = c_int |
|
|
|
_list.argtypes = [magic_t, c_char_p] |
|
|
|
|
|
|
|
_errno = _libraries['magic'].magic_errno |
|
|
|
_errno.restype = c_int |
|
|
|
_errno.argtypes = [magic_t] |
|
|
|
|
|
|
|
_getparam = _libraries['magic'].magic_getparam |
|
|
|
_getparam.restype = c_int |
|
|
|
_getparam.argtypes = [magic_t, c_int, c_void_p] |
|
|
|
|
|
|
|
_setparam = _libraries['magic'].magic_setparam |
|
|
|
_setparam.restype = c_int |
|
|
|
_setparam.argtypes = [magic_t, c_int, c_void_p] |
|
|
|
|
|
|
|
|
|
|
|
class Magic(object): |
|
|
|
def __init__(self, ms): |
|
|
|
self._magic_t = ms |
|
|
|
|
|
|
|
def close(self): |
|
|
|
""" |
|
|
|
Closes the magic database and deallocates any resources used. |
|
|
|
""" |
|
|
|
_close(self._magic_t) |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def __tostr(s): |
|
|
|
if s is None: |
|
|
|
return None |
|
|
|
if isinstance(s, str): |
|
|
|
return s |
|
|
|
try: # keep Python 2 compatibility |
|
|
|
return str(s, 'utf-8') |
|
|
|
except TypeError: |
|
|
|
return str(s) |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def __tobytes(b): |
|
|
|
if b is None: |
|
|
|
return None |
|
|
|
if isinstance(b, bytes): |
|
|
|
return b |
|
|
|
try: # keep Python 2 compatibility |
|
|
|
return bytes(b, 'utf-8') |
|
|
|
except TypeError: |
|
|
|
return bytes(b) |
|
|
|
|
|
|
|
def file(self, filename): |
|
|
|
""" |
|
|
|
Returns a textual description of the contents of the argument passed |
|
|
|
as a filename or None if an error occurred and the MAGIC_ERROR flag |
|
|
|
is set. A call to errno() will return the numeric error code. |
|
|
|
""" |
|
|
|
return Magic.__tostr(_file(self._magic_t, Magic.__tobytes(filename))) |
|
|
|
|
|
|
|
def descriptor(self, fd): |
|
|
|
""" |
|
|
|
Returns a textual description of the contents of the argument passed |
|
|
|
as a file descriptor or None if an error occurred and the MAGIC_ERROR |
|
|
|
flag is set. A call to errno() will return the numeric error code. |
|
|
|
""" |
|
|
|
return Magic.__tostr(_descriptor(self._magic_t, fd)) |
|
|
|
|
|
|
|
def buffer(self, buf): |
|
|
|
""" |
|
|
|
Returns a textual description of the contents of the argument passed |
|
|
|
as a buffer or None if an error occurred and the MAGIC_ERROR flag |
|
|
|
is set. A call to errno() will return the numeric error code. |
|
|
|
""" |
|
|
|
return Magic.__tostr(_buffer(self._magic_t, buf, len(buf))) |
|
|
|
|
|
|
|
def error(self): |
|
|
|
""" |
|
|
|
Returns a textual explanation of the last error or None |
|
|
|
if there was no error. |
|
|
|
""" |
|
|
|
return Magic.__tostr(_error(self._magic_t)) |
|
|
|
|
|
|
|
def setflags(self, flags): |
|
|
|
""" |
|
|
|
Set flags on the magic object which determine how magic checking |
|
|
|
behaves; a bitwise OR of the flags described in libmagic(3), but |
|
|
|
without the MAGIC_ prefix. |
|
|
|
|
|
|
|
Returns -1 on systems that don't support utime(2) or utimes(2) |
|
|
|
when PRESERVE_ATIME is set. |
|
|
|
""" |
|
|
|
return _setflags(self._magic_t, flags) |
|
|
|
|
|
|
|
def load(self, filename=None): |
|
|
|
""" |
|
|
|
Must be called to load entries in the colon separated list of database |
|
|
|
files passed as argument or the default database file if no argument |
|
|
|
before any magic queries can be performed. |
|
|
|
|
|
|
|
Returns 0 on success and -1 on failure. |
|
|
|
""" |
|
|
|
return _load(self._magic_t, Magic.__tobytes(filename)) |
|
|
|
|
|
|
|
def compile(self, dbs): |
|
|
|
""" |
|
|
|
Compile entries in the colon separated list of database files |
|
|
|
passed as argument or the default database file if no argument. |
|
|
|
The compiled files created are named from the basename(1) of each file |
|
|
|
argument with ".mgc" appended to it. |
|
|
|
|
|
|
|
Returns 0 on success and -1 on failure. |
|
|
|
""" |
|
|
|
return _compile(self._magic_t, Magic.__tobytes(dbs)) |
|
|
|
|
|
|
|
def check(self, dbs): |
|
|
|
""" |
|
|
|
Check the validity of entries in the colon separated list of |
|
|
|
database files passed as argument or the default database file |
|
|
|
if no argument. |
|
|
|
|
|
|
|
Returns 0 on success and -1 on failure. |
|
|
|
""" |
|
|
|
return _check(self._magic_t, Magic.__tobytes(dbs)) |
|
|
|
|
|
|
|
def list(self, dbs): |
|
|
|
""" |
|
|
|
Check the validity of entries in the colon separated list of |
|
|
|
database files passed as argument or the default database file |
|
|
|
if no argument. |
|
|
|
|
|
|
|
Returns 0 on success and -1 on failure. |
|
|
|
""" |
|
|
|
return _list(self._magic_t, Magic.__tobytes(dbs)) |
|
|
|
|
|
|
|
def errno(self): |
|
|
|
""" |
|
|
|
Returns a numeric error code. If return value is 0, an internal |
|
|
|
magic error occurred. If return value is non-zero, the value is |
|
|
|
an OS error code. Use the errno module or os.strerror() can be used |
|
|
|
to provide detailed error information. |
|
|
|
""" |
|
|
|
return _errno(self._magic_t) |
|
|
|
|
|
|
|
def getparam(self, param): |
|
|
|
""" |
|
|
|
Returns the param value if successful and -1 if the parameter |
|
|
|
was unknown. |
|
|
|
""" |
|
|
|
v = c_int() |
|
|
|
i = _getparam(self._magic_t, param, byref(v)) |
|
|
|
if i == -1: |
|
|
|
return -1 |
|
|
|
return v.value |
|
|
|
|
|
|
|
def setparam(self, param, value): |
|
|
|
""" |
|
|
|
Returns 0 if successful and -1 if the parameter was unknown. |
|
|
|
""" |
|
|
|
v = c_int(value) |
|
|
|
return _setparam(self._magic_t, param, byref(v)) |
|
|
|
|
|
|
|
|
|
|
|
def open(flags): |
|
|
|
""" |
|
|
|
Returns a magic object on success and None on failure. |
|
|
|
Flags argument as for setflags. |
|
|
|
""" |
|
|
|
magic_t = _open(flags) |
|
|
|
if magic_t is None: |
|
|
|
return None |
|
|
|
return Magic(magic_t) |
|
|
|
|
|
|
|
|
|
|
|
# Objects used by `detect_from_` functions |
|
|
|
class error(Exception): |
|
|
|
pass |
|
|
|
|
|
|
|
class MagicDetect(object): |
|
|
|
def __init__(self): |
|
|
|
self.mime_magic = open(MAGIC_MIME) |
|
|
|
if self.mime_magic is None: |
|
|
|
raise error |
|
|
|
if self.mime_magic.load() == -1: |
|
|
|
self.mime_magic.close() |
|
|
|
self.mime_magic = None |
|
|
|
raise error |
|
|
|
self.none_magic = open(MAGIC_NONE) |
|
|
|
if self.none_magic is None: |
|
|
|
self.mime_magic.close() |
|
|
|
self.mime_magic = None |
|
|
|
raise error |
|
|
|
if self.none_magic.load() == -1: |
|
|
|
self.none_magic.close() |
|
|
|
self.none_magic = None |
|
|
|
self.mime_magic.close() |
|
|
|
self.mime_magic = None |
|
|
|
raise error |
|
|
|
|
|
|
|
def __del__(self): |
|
|
|
if self.mime_magic is not None: |
|
|
|
self.mime_magic.close() |
|
|
|
if self.none_magic is not None: |
|
|
|
self.none_magic.close() |
|
|
|
|
|
|
|
threadlocal = threading.local() |
|
|
|
|
|
|
|
def _detect_make(): |
|
|
|
v = getattr(threadlocal, "magic_instance", None) |
|
|
|
if v is None: |
|
|
|
v = MagicDetect() |
|
|
|
setattr(threadlocal, "magic_instance", v) |
|
|
|
return v |
|
|
|
|
|
|
|
def _create_filemagic(mime_detected, type_detected): |
|
|
|
try: |
|
|
|
mime_type, mime_encoding = mime_detected.split('; ') |
|
|
|
except ValueError: |
|
|
|
raise ValueError(mime_detected) |
|
|
|
|
|
|
|
return FileMagic(name=type_detected, mime_type=mime_type, |
|
|
|
encoding=mime_encoding.replace('charset=', '')) |
|
|
|
|
|
|
|
|
|
|
|
def detect_from_filename(filename): |
|
|
|
'''Detect mime type, encoding and file type from a filename |
|
|
|
|
|
|
|
Returns a `FileMagic` namedtuple. |
|
|
|
''' |
|
|
|
x = _detect_make() |
|
|
|
return _create_filemagic(x.mime_magic.file(filename), |
|
|
|
x.none_magic.file(filename)) |
|
|
|
|
|
|
|
|
|
|
|
def detect_from_fobj(fobj): |
|
|
|
'''Detect mime type, encoding and file type from file-like object |
|
|
|
|
|
|
|
Returns a `FileMagic` namedtuple. |
|
|
|
''' |
|
|
|
|
|
|
|
file_descriptor = fobj.fileno() |
|
|
|
x = _detect_make() |
|
|
|
return _create_filemagic(x.mime_magic.descriptor(file_descriptor), |
|
|
|
x.none_magic.descriptor(file_descriptor)) |
|
|
|
|
|
|
|
|
|
|
|
def detect_from_content(byte_content): |
|
|
|
'''Detect mime type, encoding and file type from bytes |
|
|
|
|
|
|
|
Returns a `FileMagic` namedtuple. |
|
|
|
''' |
|
|
|
|
|
|
|
x = _detect_make() |
|
|
|
return _create_filemagic(x.mime_magic.buffer(byte_content), |
|
|
|
x.none_magic.buffer(byte_content)) |