xref: /freebsd/contrib/file/python/magic.py (revision 73e0d6b44038d1c7764c5013a54ae17a8f680a69)
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	self._close = _close
131
132    def close(self):
133        """
134        Closes the magic database and deallocates any resources used.
135        """
136        if _close:
137            _close(self._magic_t)
138
139    @staticmethod
140    def __tostr(s):
141        if s is None:
142            return None
143        if isinstance(s, str):
144            return s
145        try:  # keep Python 2 compatibility
146            return str(s, 'utf-8')
147        except TypeError:
148            return str(s)
149
150    @staticmethod
151    def __tobytes(b):
152        if b is None:
153            return None
154        if isinstance(b, bytes):
155            return b
156        try:  # keep Python 2 compatibility
157            return bytes(b, 'utf-8')
158        except TypeError:
159            return bytes(b)
160
161    def file(self, filename):
162        """
163        Returns a textual description of the contents of the argument passed
164        as a filename or None if an error occurred and the MAGIC_ERROR flag
165        is set. A call to errno() will return the numeric error code.
166        """
167        return Magic.__tostr(_file(self._magic_t, Magic.__tobytes(filename)))
168
169    def descriptor(self, fd):
170        """
171        Returns a textual description of the contents of the argument passed
172        as a file descriptor or None if an error occurred and the MAGIC_ERROR
173        flag is set. A call to errno() will return the numeric error code.
174        """
175        return Magic.__tostr(_descriptor(self._magic_t, fd))
176
177    def buffer(self, buf):
178        """
179        Returns a textual description of the contents of the argument passed
180        as a buffer or None if an error occurred and the MAGIC_ERROR flag
181        is set. A call to errno() will return the numeric error code.
182        """
183        return Magic.__tostr(_buffer(self._magic_t, buf, len(buf)))
184
185    def error(self):
186        """
187        Returns a textual explanation of the last error or None
188        if there was no error.
189        """
190        return Magic.__tostr(_error(self._magic_t))
191
192    def setflags(self, flags):
193        """
194        Set flags on the magic object which determine how magic checking
195        behaves; a bitwise OR of the flags described in libmagic(3), but
196        without the MAGIC_ prefix.
197
198        Returns -1 on systems that don't support utime(2) or utimes(2)
199        when PRESERVE_ATIME is set.
200        """
201        return _setflags(self._magic_t, flags)
202
203    def load(self, filename=None):
204        """
205        Must be called to load entries in the colon separated list of database
206        files passed as argument or the default database file if no argument
207        before any magic queries can be performed.
208
209        Returns 0 on success and -1 on failure.
210        """
211        return _load(self._magic_t, Magic.__tobytes(filename))
212
213    def compile(self, dbs):
214        """
215        Compile entries in the colon separated list of database files
216        passed as argument or the default database file if no argument.
217        The compiled files created are named from the basename(1) of each file
218        argument with ".mgc" appended to it.
219
220        Returns 0 on success and -1 on failure.
221        """
222        return _compile(self._magic_t, Magic.__tobytes(dbs))
223
224    def check(self, dbs):
225        """
226        Check the validity of entries in the colon separated list of
227        database files passed as argument or the default database file
228        if no argument.
229
230        Returns 0 on success and -1 on failure.
231        """
232        return _check(self._magic_t, Magic.__tobytes(dbs))
233
234    def list(self, dbs):
235        """
236        Check the validity of entries in the colon separated list of
237        database files passed as argument or the default database file
238        if no argument.
239
240        Returns 0 on success and -1 on failure.
241        """
242        return _list(self._magic_t, Magic.__tobytes(dbs))
243
244    def errno(self):
245        """
246        Returns a numeric error code. If return value is 0, an internal
247        magic error occurred. If return value is non-zero, the value is
248        an OS error code. Use the errno module or os.strerror() can be used
249        to provide detailed error information.
250        """
251        return _errno(self._magic_t)
252
253    def getparam(self, param):
254        """
255        Returns the param value if successful and -1 if the parameter
256        was unknown.
257        """
258        v = c_int()
259        i = _getparam(self._magic_t, param, byref(v))
260        if i == -1:
261            return -1
262        return v.value
263
264    def setparam(self, param, value):
265        """
266        Returns 0 if successful and -1 if the parameter was unknown.
267        """
268        v = c_int(value)
269        return _setparam(self._magic_t, param, byref(v))
270
271
272def open(flags):
273    """
274    Returns a magic object on success and None on failure.
275    Flags argument as for setflags.
276    """
277    magic_t = _open(flags)
278    if magic_t is None:
279        return None
280    return Magic(magic_t)
281
282
283# Objects used by `detect_from_` functions
284class error(Exception):
285    pass
286
287class MagicDetect(object):
288    def __init__(self):
289        self.mime_magic = open(MAGIC_MIME)
290        if self.mime_magic is None:
291            raise error
292        if self.mime_magic.load() == -1:
293            self.mime_magic.close()
294            self.mime_magic = None
295            raise error
296        self.none_magic = open(MAGIC_NONE)
297        if self.none_magic is None:
298            self.mime_magic.close()
299            self.mime_magic = None
300            raise error
301        if self.none_magic.load() == -1:
302            self.none_magic.close()
303            self.none_magic = None
304            self.mime_magic.close()
305            self.mime_magic = None
306            raise error
307
308    def __del__(self):
309        if self.mime_magic is not None:
310            self.mime_magic.close()
311        if self.none_magic is not None:
312            self.none_magic.close()
313
314threadlocal = threading.local()
315
316def _detect_make():
317    v = getattr(threadlocal, "magic_instance", None)
318    if v is None:
319        v = MagicDetect()
320        setattr(threadlocal, "magic_instance", v)
321    return v
322
323def _create_filemagic(mime_detected, type_detected):
324    try:
325        mime_type, mime_encoding = mime_detected.split('; ')
326    except ValueError:
327        raise ValueError(mime_detected)
328
329    return FileMagic(name=type_detected, mime_type=mime_type,
330                     encoding=mime_encoding.replace('charset=', ''))
331
332
333def detect_from_filename(filename):
334    '''Detect mime type, encoding and file type from a filename
335
336    Returns a `FileMagic` namedtuple.
337    '''
338    x = _detect_make()
339    return _create_filemagic(x.mime_magic.file(filename),
340                             x.none_magic.file(filename))
341
342
343def detect_from_fobj(fobj):
344    '''Detect mime type, encoding and file type from file-like object
345
346    Returns a `FileMagic` namedtuple.
347    '''
348
349    file_descriptor = fobj.fileno()
350    x = _detect_make()
351    return _create_filemagic(x.mime_magic.descriptor(file_descriptor),
352                             x.none_magic.descriptor(file_descriptor))
353
354
355def detect_from_content(byte_content):
356    '''Detect mime type, encoding and file type from bytes
357
358    Returns a `FileMagic` namedtuple.
359    '''
360
361    x = _detect_make()
362    return _create_filemagic(x.mime_magic.buffer(byte_content),
363                             x.none_magic.buffer(byte_content))
364