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