1# coding: utf-8
2
3'''
4Python bindings for libmagic
5'''
6
7import ctypes
8
9from collections import namedtuple
10
11from ctypes import *
12from ctypes.util import find_library
13
14
15def _init():
16    """
17    Loads the shared library through ctypes and returns a library
18    L{ctypes.CDLL} instance
19    """
20    return ctypes.cdll.LoadLibrary(find_library('magic'))
21
22_libraries = {}
23_libraries['magic'] = _init()
24
25# Flag constants for open and setflags
26MAGIC_NONE = NONE = 0
27MAGIC_DEBUG = DEBUG = 1
28MAGIC_SYMLINK = SYMLINK = 2
29MAGIC_COMPRESS = COMPRESS = 4
30MAGIC_DEVICES = DEVICES = 8
31MAGIC_MIME_TYPE = MIME_TYPE = 16
32MAGIC_CONTINUE = CONTINUE = 32
33MAGIC_CHECK = CHECK = 64
34MAGIC_PRESERVE_ATIME = PRESERVE_ATIME = 128
35MAGIC_RAW = RAW = 256
36MAGIC_ERROR = ERROR = 512
37MAGIC_MIME_ENCODING = MIME_ENCODING = 1024
38MAGIC_MIME = MIME = 1040  # MIME_TYPE + MIME_ENCODING
39MAGIC_APPLE = APPLE = 2048
40
41MAGIC_NO_CHECK_COMPRESS = NO_CHECK_COMPRESS = 4096
42MAGIC_NO_CHECK_TAR = NO_CHECK_TAR = 8192
43MAGIC_NO_CHECK_SOFT = NO_CHECK_SOFT = 16384
44MAGIC_NO_CHECK_APPTYPE = NO_CHECK_APPTYPE = 32768
45MAGIC_NO_CHECK_ELF = NO_CHECK_ELF = 65536
46MAGIC_NO_CHECK_TEXT = NO_CHECK_TEXT = 131072
47MAGIC_NO_CHECK_CDF = NO_CHECK_CDF = 262144
48MAGIC_NO_CHECK_TOKENS = NO_CHECK_TOKENS = 1048576
49MAGIC_NO_CHECK_ENCODING = NO_CHECK_ENCODING = 2097152
50
51MAGIC_NO_CHECK_BUILTIN = NO_CHECK_BUILTIN = 4173824
52
53FileMagic = namedtuple('FileMagic', ('mime_type', 'encoding', 'name'))
54
55
56class magic_set(Structure):
57    pass
58magic_set._fields_ = []
59magic_t = POINTER(magic_set)
60
61_open = _libraries['magic'].magic_open
62_open.restype = magic_t
63_open.argtypes = [c_int]
64
65_close = _libraries['magic'].magic_close
66_close.restype = None
67_close.argtypes = [magic_t]
68
69_file = _libraries['magic'].magic_file
70_file.restype = c_char_p
71_file.argtypes = [magic_t, c_char_p]
72
73_descriptor = _libraries['magic'].magic_descriptor
74_descriptor.restype = c_char_p
75_descriptor.argtypes = [magic_t, c_int]
76
77_buffer = _libraries['magic'].magic_buffer
78_buffer.restype = c_char_p
79_buffer.argtypes = [magic_t, c_void_p, c_size_t]
80
81_error = _libraries['magic'].magic_error
82_error.restype = c_char_p
83_error.argtypes = [magic_t]
84
85_setflags = _libraries['magic'].magic_setflags
86_setflags.restype = c_int
87_setflags.argtypes = [magic_t, c_int]
88
89_load = _libraries['magic'].magic_load
90_load.restype = c_int
91_load.argtypes = [magic_t, c_char_p]
92
93_compile = _libraries['magic'].magic_compile
94_compile.restype = c_int
95_compile.argtypes = [magic_t, c_char_p]
96
97_check = _libraries['magic'].magic_check
98_check.restype = c_int
99_check.argtypes = [magic_t, c_char_p]
100
101_list = _libraries['magic'].magic_list
102_list.restype = c_int
103_list.argtypes = [magic_t, c_char_p]
104
105_errno = _libraries['magic'].magic_errno
106_errno.restype = c_int
107_errno.argtypes = [magic_t]
108
109
110class Magic(object):
111    def __init__(self, ms):
112        self._magic_t = ms
113
114    def close(self):
115        """
116        Closes the magic database and deallocates any resources used.
117        """
118        _close(self._magic_t)
119
120    @staticmethod
121    def __tostr(s):
122        if s is None:
123            return None
124        if isinstance(s, str):
125            return s
126        try:  # keep Python 2 compatibility
127            return str(s, 'utf-8')
128        except TypeError:
129            return str(s)
130
131    @staticmethod
132    def __tobytes(b):
133        if b is None:
134            return None
135        if isinstance(b, bytes):
136            return b
137        try:  # keep Python 2 compatibility
138            return bytes(b, 'utf-8')
139        except TypeError:
140            return bytes(b)
141
142    def file(self, filename):
143        """
144        Returns a textual description of the contents of the argument passed
145        as a filename or None if an error occurred and the MAGIC_ERROR flag
146        is set. A call to errno() will return the numeric error code.
147        """
148        return Magic.__tostr(_file(self._magic_t, Magic.__tobytes(filename)))
149
150    def descriptor(self, fd):
151        """
152        Returns a textual description of the contents of the argument passed
153        as a file descriptor or None if an error occurred and the MAGIC_ERROR
154        flag is set. A call to errno() will return the numeric error code.
155        """
156        return Magic.__tostr(_descriptor(self._magic_t, fd))
157
158    def buffer(self, buf):
159        """
160        Returns a textual description of the contents of the argument passed
161        as a buffer or None if an error occurred and the MAGIC_ERROR flag
162        is set. A call to errno() will return the numeric error code.
163        """
164        return Magic.__tostr(_buffer(self._magic_t, buf, len(buf)))
165
166    def error(self):
167        """
168        Returns a textual explanation of the last error or None
169        if there was no error.
170        """
171        return Magic.__tostr(_error(self._magic_t))
172
173    def setflags(self, flags):
174        """
175        Set flags on the magic object which determine how magic checking
176        behaves; a bitwise OR of the flags described in libmagic(3), but
177        without the MAGIC_ prefix.
178
179        Returns -1 on systems that don't support utime(2) or utimes(2)
180        when PRESERVE_ATIME is set.
181        """
182        return _setflags(self._magic_t, flags)
183
184    def load(self, filename=None):
185        """
186        Must be called to load entries in the colon separated list of database
187        files passed as argument or the default database file if no argument
188        before any magic queries can be performed.
189
190        Returns 0 on success and -1 on failure.
191        """
192        return _load(self._magic_t, Magic.__tobytes(filename))
193
194    def compile(self, dbs):
195        """
196        Compile entries in the colon separated list of database files
197        passed as argument or the default database file if no argument.
198        The compiled files created are named from the basename(1) of each file
199        argument with ".mgc" appended to it.
200
201        Returns 0 on success and -1 on failure.
202        """
203        return _compile(self._magic_t, Magic.__tobytes(dbs))
204
205    def check(self, dbs):
206        """
207        Check the validity of entries in the colon separated list of
208        database files passed as argument or the default database file
209        if no argument.
210
211        Returns 0 on success and -1 on failure.
212        """
213        return _check(self._magic_t, Magic.__tobytes(dbs))
214
215    def list(self, dbs):
216        """
217        Check the validity of entries in the colon separated list of
218        database files passed as argument or the default database file
219        if no argument.
220
221        Returns 0 on success and -1 on failure.
222        """
223        return _list(self._magic_t, Magic.__tobytes(dbs))
224
225    def errno(self):
226        """
227        Returns a numeric error code. If return value is 0, an internal
228        magic error occurred. If return value is non-zero, the value is
229        an OS error code. Use the errno module or os.strerror() can be used
230        to provide detailed error information.
231        """
232        return _errno(self._magic_t)
233
234
235def open(flags):
236    """
237    Returns a magic object on success and None on failure.
238    Flags argument as for setflags.
239    """
240    return Magic(_open(flags))
241
242
243# Objects used by `detect_from_` functions
244mime_magic = Magic(_open(MAGIC_MIME))
245mime_magic.load()
246none_magic = Magic(_open(MAGIC_NONE))
247none_magic.load()
248
249
250def _create_filemagic(mime_detected, type_detected):
251    try:
252        mime_type, mime_encoding = mime_detected.split('; ')
253    except ValueError:
254        raise ValueError(mime_detected)
255
256    return FileMagic(name=type_detected, mime_type=mime_type,
257                     encoding=mime_encoding.replace('charset=', ''))
258
259
260def detect_from_filename(filename):
261    '''Detect mime type, encoding and file type from a filename
262
263    Returns a `FileMagic` namedtuple.
264    '''
265
266    return _create_filemagic(mime_magic.file(filename),
267                             none_magic.file(filename))
268
269
270def detect_from_fobj(fobj):
271    '''Detect mime type, encoding and file type from file-like object
272
273    Returns a `FileMagic` namedtuple.
274    '''
275
276    file_descriptor = fobj.fileno()
277    return _create_filemagic(mime_magic.descriptor(file_descriptor),
278                             none_magic.descriptor(file_descriptor))
279
280
281def detect_from_content(byte_content):
282    '''Detect mime type, encoding and file type from bytes
283
284    Returns a `FileMagic` namedtuple.
285    '''
286
287    return _create_filemagic(mime_magic.buffer(byte_content),
288                             none_magic.buffer(byte_content))
289