magic.py revision 300899
150276Speter# coding: utf-8
2178866Srafan
350276Speter'''
450276SpeterPython bindings for libmagic
550276Speter'''
650276Speter
750276Speterimport ctypes
850276Speter
950276Speterfrom collections import namedtuple
1050276Speter
1150276Speterfrom ctypes import *
1250276Speterfrom ctypes.util import find_library
1350276Speter
1450276Speter
1550276Speterdef _init():
1650276Speter    """
1750276Speter    Loads the shared library through ctypes and returns a library
1850276Speter    L{ctypes.CDLL} instance
1950276Speter    """
2050276Speter    return ctypes.cdll.LoadLibrary(find_library('magic'))
2150276Speter
2250276Speter_libraries = {}
2350276Speter_libraries['magic'] = _init()
2450276Speter
2550276Speter# Flag constants for open and setflags
2650276SpeterMAGIC_NONE = NONE = 0
2750276SpeterMAGIC_DEBUG = DEBUG = 1
2850276SpeterMAGIC_SYMLINK = SYMLINK = 2
2950276SpeterMAGIC_COMPRESS = COMPRESS = 4
3050276SpeterMAGIC_DEVICES = DEVICES = 8
3150276SpeterMAGIC_MIME_TYPE = MIME_TYPE = 16
3250276SpeterMAGIC_CONTINUE = CONTINUE = 32
3350276SpeterMAGIC_CHECK = CHECK = 64
3450276SpeterMAGIC_PRESERVE_ATIME = PRESERVE_ATIME = 128
3550276SpeterMAGIC_RAW = RAW = 256
3650276SpeterMAGIC_ERROR = ERROR = 512
3750276SpeterMAGIC_MIME_ENCODING = MIME_ENCODING = 1024
3850276SpeterMAGIC_MIME = MIME = 1040  # MIME_TYPE + MIME_ENCODING
3976726SpeterMAGIC_APPLE = APPLE = 2048
4050276Speter
41184989SrafanMAGIC_NO_CHECK_COMPRESS = NO_CHECK_COMPRESS = 4096
4250276SpeterMAGIC_NO_CHECK_TAR = NO_CHECK_TAR = 8192
4376726SpeterMAGIC_NO_CHECK_SOFT = NO_CHECK_SOFT = 16384
4476726SpeterMAGIC_NO_CHECK_APPTYPE = NO_CHECK_APPTYPE = 32768
4550276SpeterMAGIC_NO_CHECK_ELF = NO_CHECK_ELF = 65536
46166124SrafanMAGIC_NO_CHECK_TEXT = NO_CHECK_TEXT = 131072
47166124SrafanMAGIC_NO_CHECK_CDF = NO_CHECK_CDF = 262144
48174993SrafanMAGIC_NO_CHECK_TOKENS = NO_CHECK_TOKENS = 1048576
49166124SrafanMAGIC_NO_CHECK_ENCODING = NO_CHECK_ENCODING = 2097152
50166124Srafan
51174993SrafanMAGIC_NO_CHECK_BUILTIN = NO_CHECK_BUILTIN = 4173824
52174993Srafan
53174993SrafanFileMagic = namedtuple('FileMagic', ('mime_type', 'encoding', 'name'))
54174993Srafan
55174993Srafan
56174993Srafanclass magic_set(Structure):
57174993Srafan    pass
58174993Srafanmagic_set._fields_ = []
59174993Srafanmagic_t = POINTER(magic_set)
60174993Srafan
61166124Srafan_open = _libraries['magic'].magic_open
62166124Srafan_open.restype = magic_t
63166124Srafan_open.argtypes = [c_int]
64174993Srafan
65174993Srafan_close = _libraries['magic'].magic_close
66166124Srafan_close.restype = None
67174993Srafan_close.argtypes = [magic_t]
68184989Srafan
69184989Srafan_file = _libraries['magic'].magic_file
70174993Srafan_file.restype = c_char_p
71166124Srafan_file.argtypes = [magic_t, c_char_p]
7250276Speter
7350276Speter_descriptor = _libraries['magic'].magic_descriptor
7476726Speter_descriptor.restype = c_char_p
7576726Speter_descriptor.argtypes = [magic_t, c_int]
7650276Speter
77166124Srafan_buffer = _libraries['magic'].magic_buffer
78166124Srafan_buffer.restype = c_char_p
79174993Srafan_buffer.argtypes = [magic_t, c_void_p, c_size_t]
80166124Srafan
81166124Srafan_error = _libraries['magic'].magic_error
82174993Srafan_error.restype = c_char_p
83174993Srafan_error.argtypes = [magic_t]
84174993Srafan
85174993Srafan_setflags = _libraries['magic'].magic_setflags
86174993Srafan_setflags.restype = c_int
87174993Srafan_setflags.argtypes = [magic_t, c_int]
88174993Srafan
89174993Srafan_load = _libraries['magic'].magic_load
90174993Srafan_load.restype = c_int
91174993Srafan_load.argtypes = [magic_t, c_char_p]
92174993Srafan
93174993Srafan_compile = _libraries['magic'].magic_compile
94166124Srafan_compile.restype = c_int
95166124Srafan_compile.argtypes = [magic_t, c_char_p]
96174993Srafan
97184989Srafan_check = _libraries['magic'].magic_check
98184989Srafan_check.restype = c_int
99166124Srafan_check.argtypes = [magic_t, c_char_p]
100166124Srafan
10150276Speter_list = _libraries['magic'].magic_list
10250276Speter_list.restype = c_int
10376726Speter_list.argtypes = [magic_t, c_char_p]
10476726Speter
10550276Speter_errno = _libraries['magic'].magic_errno
106184989Srafan_errno.restype = c_int
107184989Srafan_errno.argtypes = [magic_t]
10876726Speter
10950276Speter
110184989Srafanclass Magic(object):
111184989Srafan    def __init__(self, ms):
112184989Srafan        self._magic_t = ms
113184989Srafan
114184989Srafan    def close(self):
11550276Speter        """
116184989Srafan        Closes the magic database and deallocates any resources used.
117184989Srafan        """
11850276Speter        _close(self._magic_t)
119184989Srafan
120184989Srafan    def file(self, filename):
12150276Speter        """
122184989Srafan        Returns a textual description of the contents of the argument passed
123184989Srafan        as a filename or None if an error occurred and the MAGIC_ERROR flag
124184989Srafan        is set.  A call to errno() will return the numeric error code.
125184989Srafan        """
12650276Speter        if isinstance(filename, bytes):
12750276Speter            bi = filename
12876726Speter        else:
12976726Speter            try:  # keep Python 2 compatibility
13050276Speter                bi = bytes(filename, 'utf-8')
131184989Srafan            except TypeError:
132184989Srafan                bi = bytes(filename)
13376726Speter        r = _file(self._magic_t, bi)
13450276Speter        if isinstance(r, str):
135184989Srafan            return r
136184989Srafan        else:
137184989Srafan            return str(r).encode('utf-8')
138184989Srafan
139184989Srafan    def descriptor(self, fd):
14050276Speter        """
141184989Srafan        Like the file method, but the argument is a file descriptor.
14250276Speter        """
143184989Srafan        return _descriptor(self._magic_t, fd)
14450276Speter
145184989Srafan    def buffer(self, buf):
146184989Srafan        """
147184989Srafan        Returns a textual description of the contents of the argument passed
148184989Srafan        as a buffer or None if an error occurred and the MAGIC_ERROR flag
14950276Speter        is set. A call to errno() will return the numeric error code.
15050276Speter        """
15176726Speter        r = _buffer(self._magic_t, buf, len(buf))
15276726Speter        if isinstance(r, str):
15350276Speter            return r
15476726Speter        else:
15550276Speter            return str(r).encode('utf-8')
15676726Speter
157166124Srafan    def error(self):
158166124Srafan        """
159166124Srafan        Returns a textual explanation of the last error or None
160178866Srafan        if there was no error.
161166124Srafan        """
162166124Srafan        e = _error(self._magic_t)
163166124Srafan        if isinstance(e, str):
16450276Speter            return e
16576726Speter        else:
16676726Speter            return str(e).encode('utf-8')
16750276Speter
16850276Speter    def setflags(self, flags):
16976726Speter        """
17076726Speter        Set flags on the magic object which determine how magic checking
17150276Speter        behaves; a bitwise OR of the flags described in libmagic(3), but
17276726Speter        without the MAGIC_ prefix.
17350276Speter
17476726Speter        Returns -1 on systems that don't support utime(2) or utimes(2)
17576726Speter        when PRESERVE_ATIME is set.
176178866Srafan        """
17776726Speter        return _setflags(self._magic_t, flags)
17876726Speter
17950276Speter    def load(self, filename=None):
18076726Speter        """
18176726Speter        Must be called to load entries in the colon separated list of database
18276726Speter        files passed as argument or the default database file if no argument
18350276Speter        before any magic queries can be performed.
18450276Speter
185174993Srafan        Returns 0 on success and -1 on failure.
186174993Srafan        """
187174993Srafan        return _load(self._magic_t, filename)
188174993Srafan
189174993Srafan    def compile(self, dbs):
190174993Srafan        """
191174993Srafan        Compile entries in the colon separated list of database files
192174993Srafan        passed as argument or the default database file if no argument.
193174993Srafan        Returns 0 on success and -1 on failure.
194174993Srafan        The compiled files created are named from the basename(1) of each file
195174993Srafan        argument with ".mgc" appended to it.
196174993Srafan        """
197174993Srafan        return _compile(self._magic_t, dbs)
198174993Srafan
199174993Srafan    def check(self, dbs):
200174993Srafan        """
20150276Speter        Check the validity of entries in the colon separated list of
20250276Speter        database files passed as argument or the default database file
20350276Speter        if no argument.
20450276Speter        Returns 0 on success and -1 on failure.
20550276Speter        """
20676726Speter        return _check(self._magic_t, dbs)
20776726Speter
20850276Speter    def list(self, dbs):
20976726Speter        """
21050276Speter        Check the validity of entries in the colon separated list of
211174993Srafan        database files passed as argument or the default database file
21250276Speter        if no argument.
21350276Speter        Returns 0 on success and -1 on failure.
21476726Speter        """
21576726Speter        return _list(self._magic_t, dbs)
21650276Speter
21776726Speter    def errno(self):
21850276Speter        """
219174993Srafan        Returns a numeric error code. If return value is 0, an internal
22050276Speter        magic error occurred. If return value is non-zero, the value is
221        an OS error code. Use the errno module or os.strerror() can be used
222        to provide detailed error information.
223        """
224        return _errno(self._magic_t)
225
226
227def open(flags):
228    """
229    Returns a magic object on success and None on failure.
230    Flags argument as for setflags.
231    """
232    return Magic(_open(flags))
233
234
235# Objects used by `detect_from_` functions
236mime_magic = Magic(_open(MAGIC_MIME))
237mime_magic.load()
238none_magic = Magic(_open(MAGIC_NONE))
239none_magic.load()
240
241
242def _create_filemagic(mime_detected, type_detected):
243    mime_type, mime_encoding = mime_detected.split('; ')
244
245    return FileMagic(name=type_detected, mime_type=mime_type,
246                     encoding=mime_encoding.replace('charset=', ''))
247
248
249def detect_from_filename(filename):
250    '''Detect mime type, encoding and file type from a filename
251
252    Returns a `FileMagic` namedtuple.
253    '''
254
255    return _create_filemagic(mime_magic.file(filename),
256                             none_magic.file(filename))
257
258
259def detect_from_fobj(fobj):
260    '''Detect mime type, encoding and file type from file-like object
261
262    Returns a `FileMagic` namedtuple.
263    '''
264
265    file_descriptor = fobj.fileno()
266    return _create_filemagic(mime_magic.descriptor(file_descriptor),
267                             none_magic.descriptor(file_descriptor))
268
269
270def detect_from_content(byte_content):
271    '''Detect mime type, encoding and file type from bytes
272
273    Returns a `FileMagic` namedtuple.
274    '''
275
276    return _create_filemagic(mime_magic.buffer(byte_content),
277                             none_magic.buffer(byte_content))
278