├── magic ├── tests │ ├── __init__.py │ ├── test_magic.py │ └── test_ffi.py ├── __init__.py ├── flags.py └── ffi.py ├── requirements.txt ├── MANIFEST.in ├── .travis.yml ├── .gitignore ├── setup.py ├── README.md └── LICENSE /magic/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cffi>=1.7 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | include setup.py 4 | include magic/__init__.py 5 | include magic/ffi.py 6 | include magic/flags.py 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.3" 5 | - "pypy" 6 | install: 7 | - sudo apt-get update 8 | - sudo apt-get install libmagic-dev 9 | - pip install -r requirements.txt 10 | script: 11 | - nosetests 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | bin/ 10 | build/ 11 | develop-eggs/ 12 | dist/ 13 | eggs/ 14 | lib/ 15 | lib64/ 16 | parts/ 17 | sdist/ 18 | var/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Installer logs 24 | pip-log.txt 25 | pip-delete-this-directory.txt 26 | 27 | # Unit test / coverage reports 28 | .tox/ 29 | .coverage 30 | .cache 31 | nosetests.xml 32 | coverage.xml 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Mr Developer 38 | .mr.developer.cfg 39 | .project 40 | .pydevproject 41 | 42 | # Rope 43 | .ropeproject 44 | 45 | # Django stuff: 46 | *.log 47 | *.pot 48 | 49 | # Sphinx documentation 50 | docs/_build/ 51 | 52 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open('requirements.txt') as f: 4 | requirements = f.read().splitlines() 5 | 6 | setup(**{ 7 | "name": "python-libmagic", 8 | "packages": ["magic"], 9 | "url": "https://github.com/dveselov/python-libmagic", 10 | "author": "Dmitry Veselov", 11 | "author_email": "d.a.veselov@yandex.ru", 12 | "version": "0.4.0", 13 | "description": "Python bindings to libmagic", 14 | "license": "MIT", 15 | "classifiers": ( 16 | "Intended Audience :: Developers", 17 | "Programming Language :: Python :: 2", 18 | "Programming Language :: Python :: 3", 19 | "Programming Language :: Python :: Implementation :: CPython", 20 | "Programming Language :: Python :: Implementation :: PyPy", 21 | ), 22 | "install_requires": requirements, 23 | }) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-libmagic 2 | 3 | ![magic.h](http://i.imgur.com/GbN8szC.jpg) 4 | 5 | # Usage 6 | 7 | ```python 8 | import magic 9 | 10 | with magic.Magic() as m: 11 | print m.from_buffer("hello") # => "text/plain" 12 | ``` 13 | 14 | ```python 15 | import magic 16 | import magic.flags 17 | 18 | magic = magic.Magic() 19 | mimetype = magic.from_buffer("\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") 20 | print mimetype # => "image/png" 21 | 22 | mimetype = magic.from_file("/etc/passwd") 23 | print mimetype # => "text/plain" 24 | 25 | updated = magic.set_flags(magic.flags.MAGIC_NONE) 26 | print updated # => True 27 | mimetype = magic.from_file("demo.docx") 28 | print mimetype # => "Microsoft Word 2007+" 29 | magic.close() # don't forget about this 30 | ``` 31 | 32 | # Installation 33 | 34 | `python-libmagic` works on CPython 2.7/3.3+ and PyPy. 35 | 36 | ```bash 37 | $ apt-get install libmagic-dev 38 | $ pip install python-libmagic 39 | ``` 40 | 41 | # License 42 | 43 | Licensed under MIT license. 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dmitry Veselov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /magic/tests/test_magic.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import magic 4 | import magic.flags 5 | 6 | 7 | class MagicTestCase(unittest.TestCase): 8 | 9 | def setUp(self): 10 | self.magic = magic.Magic(magic.flags.MAGIC_NONE) 11 | 12 | def test_get_version(self): 13 | self.assertTrue(isinstance(self.magic.version, int)) 14 | 15 | def test_from_buffer(self): 16 | mimetype = self.magic.from_buffer("ehlo") 17 | self.assertEqual(mimetype, "ASCII text, with no line terminators") 18 | 19 | def test_from_file(self): 20 | mimetype = self.magic.from_file("/etc/passwd") 21 | self.assertEqual(mimetype, "ASCII text") 22 | 23 | def test_with(self): 24 | with magic.Magic() as m: 25 | self.magic.set_flags(magic.flags.MAGIC_MIME_TYPE) 26 | mimetype = self.magic.from_file("/etc/passwd") 27 | self.assertEqual(mimetype, "text/plain") 28 | 29 | def test_set_flags(self): 30 | mimetype = self.magic.from_file("/etc/passwd") 31 | self.assertEqual(mimetype, "ASCII text") 32 | self.magic.set_flags(magic.flags.MAGIC_MIME_TYPE) 33 | mimetype = self.magic.from_file("/etc/passwd") 34 | self.assertEqual(mimetype, "text/plain") 35 | -------------------------------------------------------------------------------- /magic/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ffi 2 | from . import flags 3 | 4 | __version__ = "0.4.0" 5 | 6 | 7 | class Magic(object): 8 | 9 | def __init__(self, initial_flags=flags.MAGIC_MIME_TYPE, database=None): 10 | cookie = ffi.open(initial_flags) 11 | if database: 12 | ffi.load(cookie, database) 13 | else: 14 | ffi.load(cookie) 15 | self.cookie = cookie 16 | 17 | def __del__(self): 18 | try: 19 | ffi.close(self.cookie) 20 | except Exception as exception: 21 | raise 22 | 23 | def __enter__(self): 24 | return self 25 | 26 | def __exit__(self, type, value, traceback): 27 | del self 28 | 29 | @property 30 | def version(self): 31 | return ffi.version() 32 | 33 | def set_flags(self, flags): 34 | return ffi.set_flags(self.cookie, flags) 35 | 36 | def handle_bytes(function): 37 | def wrapper(self, value): 38 | if not isinstance(value, bytes): 39 | value = value.encode("utf-8") 40 | response = function(self, value) 41 | return response.decode("utf-8") 42 | return wrapper 43 | 44 | @handle_bytes 45 | def from_file(self, filepath): 46 | return ffi.file(self.cookie, filepath) 47 | 48 | @handle_bytes 49 | def from_buffer(self, value): 50 | return ffi.buffer(self.cookie, value) 51 | -------------------------------------------------------------------------------- /magic/flags.py: -------------------------------------------------------------------------------- 1 | MAGIC_NONE = 0x000000 # No flags 2 | MAGIC_DEBUG = 0x000001 # Turn on debugging 3 | MAGIC_SYMLINK = 0x000002 # Follow symlinks 4 | MAGIC_COMPRESS = 0x000004 # Check inside compressed files 5 | MAGIC_DEVICES = 0x000008 # Look at the contents of devices 6 | MAGIC_MIME_TYPE = 0x000010 # Return the MIME type 7 | MAGIC_CONTINUE = 0x000020 # Return all matches 8 | MAGIC_CHECK = 0x000040 # Print warnings to stderr 9 | MAGIC_PRESERVE_ATIME = 0x000080 # Restore access time on exit 10 | MAGIC_RAW = 0x000100 # Don't translate unprintable chars 11 | MAGIC_ERROR = 0x000200 # Handle ENOENT etc as real errors 12 | MAGIC_MIME_ENCODING = 0x000400 # Return the MIME encoding 13 | MAGIC_MIME = (MAGIC_MIME_TYPE|MAGIC_MIME_ENCODING) 14 | MAGIC_APPLE = 0x000800 # Return the Apple creator and type 15 | 16 | MAGIC_NO_CHECK_COMPRESS = 0x001000 # Don't check for compressed files 17 | MAGIC_NO_CHECK_TAR = 0x002000 # Don't check for tar files 18 | MAGIC_NO_CHECK_SOFT = 0x004000 # Don't check magic entries 19 | MAGIC_NO_CHECK_APPTYPE = 0x008000 # Don't check application type 20 | MAGIC_NO_CHECK_ELF = 0x010000 # Don't check for elf details 21 | MAGIC_NO_CHECK_TEXT = 0x020000 # Don't check for text files 22 | MAGIC_NO_CHECK_CDF = 0x040000 # Don't check for cdf files 23 | MAGIC_NO_CHECK_TOKENS = 0x100000 # Don't check tokens 24 | MAGIC_NO_CHECK_ENCODING = 0x200000 # Don't check text encodings 25 | 26 | MAGIC_NO_CHECK_FORTRAN = 0x000000 # Don't check ascii/fortran 27 | MAGIC_NO_CHECK_TROFF = 0x000000 # Don't check ascii/troff 28 | -------------------------------------------------------------------------------- /magic/ffi.py: -------------------------------------------------------------------------------- 1 | from cffi import FFI 2 | from . import flags 3 | 4 | 5 | ffi = FFI() 6 | ffi.cdef(""" 7 | typedef ... magic_set; 8 | typedef struct magic_set *magic_t; 9 | magic_t magic_open(int); 10 | void magic_close(magic_t); 11 | const char *magic_getpath(const char *, int); 12 | const char *magic_file(magic_t, const char *); 13 | const char *magic_descriptor(magic_t, int); 14 | const char *magic_buffer(magic_t, const void *, size_t); 15 | const char *magic_error(magic_t); 16 | int magic_setflags(magic_t, int); 17 | int magic_version(void); 18 | int magic_load(magic_t, const char *); 19 | int magic_compile(magic_t, const char *); 20 | int magic_check(magic_t, const char *); 21 | int magic_list(magic_t, const char *); 22 | int magic_errno(magic_t); 23 | """) 24 | 25 | magic = ffi.verify("#include ", 26 | libraries=["magic"], 27 | ext_package="magic") 28 | 29 | def handle_null_exception(function): 30 | def wrapper(cookie, *args, **kwargs): 31 | response = function(cookie, *args, **kwargs) 32 | if response == ffi.NULL: 33 | message = error(cookie) 34 | raise ValueError(message) 35 | else: 36 | return ffi.string(response) 37 | return wrapper 38 | 39 | def version(): 40 | return magic.magic_version() 41 | 42 | def set_flags(cookie, flags): 43 | status = magic.magic_setflags(cookie, flags) 44 | if status != 0: 45 | message = error(cookie) 46 | raise ValueError(message) 47 | else: 48 | return True 49 | 50 | def error(cookie): 51 | message = magic.magic_error(cookie) 52 | return ffi.string(message) 53 | 54 | def open(flags): 55 | cookie = magic.magic_open(flags) 56 | if cookie == ffi.NULL: 57 | message = error(cookie) 58 | raise RuntimeError(message) 59 | else: 60 | return cookie 61 | 62 | def close(cookie): 63 | closed = magic.magic_close(cookie) 64 | return True 65 | 66 | def load(cookie, path=ffi.NULL): 67 | status = magic.magic_load(cookie, path) 68 | if status != 0: 69 | message = error(cookie) 70 | raise ValueError(message) 71 | else: 72 | return True 73 | 74 | @handle_null_exception 75 | def file(cookie, path): 76 | mimetype = magic.magic_file(cookie, path) 77 | return mimetype 78 | 79 | @handle_null_exception 80 | def buffer(cookie, value): 81 | mimetype = magic.magic_buffer(cookie, value, len(value)) 82 | return mimetype 83 | -------------------------------------------------------------------------------- /magic/tests/test_ffi.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import magic.ffi 3 | import magic.flags 4 | 5 | 6 | class FFITestCase(unittest.TestCase): 7 | 8 | def test_version(self): 9 | version = magic.ffi.version() 10 | self.assertTrue(isinstance(version, int)) 11 | 12 | def test_open(self): 13 | cookie = magic.ffi.open(magic.flags.MAGIC_NONE) 14 | self.assertNotEqual(cookie, None) 15 | 16 | def test_close(self): 17 | cookie = magic.ffi.open(magic.flags.MAGIC_NONE) 18 | self.assertNotEqual(cookie, None) 19 | closed = magic.ffi.close(cookie) 20 | self.assertTrue(closed) 21 | 22 | def test_load(self): 23 | cookie = magic.ffi.open(magic.flags.MAGIC_NONE) 24 | self.assertNotEqual(cookie, None) 25 | with self.assertRaises(ValueError): 26 | loaded = magic.ffi.load(cookie, b"/etc/magic_database") 27 | loaded = magic.ffi.load(cookie) 28 | self.assertTrue(loaded) 29 | 30 | def test_file(self): 31 | cookie = magic.ffi.open(magic.flags.MAGIC_NONE) 32 | self.assertNotEqual(cookie, None) 33 | loaded = magic.ffi.load(cookie) 34 | self.assertTrue(loaded) 35 | mimetype = magic.ffi.file(cookie, b"/etc/passwd") 36 | self.assertEqual(mimetype, b"ASCII text") 37 | 38 | def test_buffer(self): 39 | cookie = magic.ffi.open(magic.flags.MAGIC_NONE) 40 | self.assertNotEqual(cookie, None) 41 | loaded = magic.ffi.load(cookie) 42 | self.assertTrue(loaded) 43 | mimetype = magic.ffi.buffer(cookie, b"") 44 | self.assertEqual(mimetype, b"empty") 45 | mimetype = magic.ffi.buffer(cookie, b"kittens") 46 | self.assertEqual(mimetype, b"ASCII text, with no line terminators") 47 | mimetype = magic.ffi.buffer(cookie, b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") 48 | self.assertEqual(mimetype, b"PNG image data") 49 | 50 | def test_set_flags(self): 51 | cookie = magic.ffi.open(magic.flags.MAGIC_NONE) 52 | self.assertNotEqual(cookie, None) 53 | loaded = magic.ffi.load(cookie) 54 | self.assertTrue(loaded) 55 | mimetype = magic.ffi.buffer(cookie, b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") 56 | self.assertEqual(mimetype, b"PNG image data") 57 | status = magic.ffi.set_flags(cookie, magic.flags.MAGIC_MIME_TYPE) 58 | self.assertTrue(status) 59 | mimetype = magic.ffi.buffer(cookie, b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") 60 | self.assertEqual(mimetype, b"image/png") 61 | status = magic.ffi.set_flags(cookie, magic.flags.MAGIC_MIME) 62 | self.assertTrue(status) 63 | mimetype = magic.ffi.buffer(cookie, b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") 64 | self.assertEqual(mimetype, b"image/png; charset=binary") 65 | --------------------------------------------------------------------------------