├── .coveragerc ├── .travis.yml ├── CHANGELOG ├── CONTRIBUTORS ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile └── source │ ├── _templates │ └── page.html │ ├── api.rst │ ├── command.rst │ ├── conf.py │ ├── guide.rst │ ├── guide_content │ └── index.rst ├── magic ├── __init__.py ├── api.py ├── command.py ├── compatability.py ├── flags.py ├── identify.py └── version.py ├── requirements ├── development.txt └── production.txt ├── setup.py └── tests ├── __init__.py ├── magic └── python └── test_magic.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source=magic 3 | omit=magic/command* 4 | 5 | [report] 6 | include=magic* 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.6 4 | - 2.7 5 | - 3.2 6 | - 3.3 7 | - pypy 8 | install: 9 | - pip install -r requirements/development.txt -r requirements/production.txt 10 | - python setup.py install 11 | script: 12 | - coverage run setup.py test 13 | - coverage report --show-missing 14 | after_success: 15 | - coveralls 16 | notifications: 17 | email: aaron.iles+travis-ci@gmail.com 18 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Changelog 2 | --------- 3 | 4 | Next release 1.7 5 | ```````````````` 6 | * Improved warning message when closing magic.Magic at garbage collection. 7 | * Published Python wheel packages to PyPI to improve install times. 8 | 9 | 1.6 (2013-05-29) 10 | ```````````````` 11 | * Stop forcing the installation of distribute. 12 | 13 | 1.5 (2012-12-18) 14 | ```````````````` 15 | * Work around for Issue 5289 with ctypes on Solaris. 16 | * Improved repr string for magic.Magic object. 17 | * Add CONTRIBUTORS file to package. 18 | 19 | 1.4 (2012-12-11) 20 | ```````````````` 21 | * Automatically clean up memory when garbage collected. 22 | * Add memory management section to documentation. 23 | 24 | 1.3 (2012-05-30) 25 | ```````````````` 26 | * Test against libmagic 5.11. 27 | 28 | 1.2 (2012-03-26) 29 | ```````````````` 30 | * Add `command line invocation `_. 31 | 32 | 1.1 (2012-03-16) 33 | ```````````````` 34 | * Sphinx documentation on `Read the Docs `_ 35 | * Prevent segmentation fault when using list() with older libmagic 36 | 37 | 1.0 (2012-03-15) 38 | ```````````````` 39 | * First version released 40 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | aliles 2 | `````` 3 | * original author and maintainer 4 | 5 | erhudy 6 | `````` 7 | * added a small patch to correct import behavior on Solaris, 8 | where ctypes.util.find_library does not function correctly. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012-2013 Aaron Iles 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include tests *.py 2 | recursive-include docs/source * 3 | include docs/Makefile 4 | include *.py 5 | include CHANGELOG 6 | include CONTRIBUTORS 7 | include LICENSE 8 | include MANIFEST.in 9 | include README.rst 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | # SET UP 4 | # install software dependencies 5 | 6 | deps: 7 | pip install --upgrade \ 8 | -r requirements/development.txt \ 9 | -r requirements/production.txt 10 | 11 | # BUILD 12 | # build and release packages 13 | 14 | build: 15 | python setup.py sdist 16 | python setup.py bdist_wheel 17 | 18 | register: 19 | python setup.py register 20 | 21 | upload: 22 | python setup.py sdist upload 23 | python setup.py bdist_wheel upload 24 | 25 | # DOCUMENTATION 26 | # build user documentation 27 | 28 | site: 29 | cd docs; make html 30 | 31 | docs: site 32 | 33 | # TESTING 34 | # unit tests, coverage testing and static type checking 35 | 36 | coverage: 37 | coverage report --show-missing 38 | 39 | lint: 40 | flake8 --exit-zero magic tests 41 | 42 | test: 43 | coverage run setup.py test 44 | 45 | unittest: 46 | coverage run -m unittest discover 47 | 48 | # CLEAN 49 | # remove build artifacts 50 | 51 | clean: 52 | python setup.py clean --all 53 | find . -type f -name "*.pyc" -exec rm '{}' + 54 | find . -type d -name "__pycache__" -exec rmdir '{}' + 55 | rm -rf *.egg-info .coverage 56 | cd docs; make clean 57 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | filemagic 2 | --------- 3 | 4 | *filemagic* is a ctypes wrapper for `libmagic 5 | `_, the library that supports the *file* 6 | command on most Unix systems. The package provides a simple Python API for 7 | identifying files using the extensive database of magic strings that ships with 8 | *libmagic*. 9 | 10 | * Documentation for *filemagic* is hosted on `Read the Docs 11 | `_. 12 | * Packages are hosted by the `Python Package Index 13 | `_. 14 | 15 | *filemagic* has been tested against *libmagic* 5.11. It supports both Python2 16 | and Python3, as well as CPython and PyPy. 17 | 18 | |build_status| |coverage| |pypi_version| 19 | 20 | Example 21 | ------- 22 | 23 | Below is a short snippet of code showing how to use *filemagic* to identifying 24 | this README file. :: 25 | 26 | >>> import magic 27 | >>> with magic.Magic() as m: 28 | ... m.id_filename('setup.py') 29 | ... 30 | 'ASCII text' 31 | 32 | It is recommended that *magic.Magic* be used with a context manager (the *with* 33 | statement) to avoid leaking resources from *libmagic* when instances go out of 34 | scope. Otherwise the *close()* method must be called explicitly. 35 | 36 | Further Reading 37 | --------------- 38 | 39 | Refer to the `filemagic documenation `_ for 40 | further references. 41 | 42 | .. |build_status| image:: https://secure.travis-ci.org/aliles/filemagic.png?branch=master 43 | :target: http://travis-ci.org/#!/aliles/filemagic 44 | :alt: Current build status 45 | 46 | .. |coverage| image:: https://coveralls.io/repos/aliles/filemagic/badge.png?branch=master 47 | :target: https://coveralls.io/r/aliles/filemagic?branch=master 48 | :alt: Coverage status 49 | 50 | .. |pypi_version| image:: https://pypip.in/v/filemagic/badge.png 51 | :target: https://crate.io/packages/filemagic/ 52 | :alt: Latest PyPI version 53 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/filemagic.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/filemagic.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/filemagic" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/filemagic" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/source/_templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "!page.html" %} 2 | {% block document %} 3 | 4 | Fork me on GitHub 7 | 8 | {{ super() }} 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | The filemagic API 2 | ================= 3 | 4 | .. module:: magic 5 | :noindex: 6 | 7 | Importing the :mod:`magic` module provides access to all *filemagic* primitives. 8 | Most importantly the :class:`~magic.Magic` class. 9 | 10 | Exceptions 11 | ---------- 12 | 13 | If something goes with *libmagic*, an exception will be raised. 14 | 15 | .. module:: magic.api 16 | :noindex: 17 | 18 | .. exception:: MagicError(errno, error) 19 | 20 | ``errno`` is the numerical error code returned by *libmagic*. ``error`` is 21 | the textual description of that error code, as supplied by *libmagic*. 22 | 23 | :exc:`~magic.MagicError` inherits from ``EnvironmentError``. 24 | 25 | Classes 26 | ------- 27 | 28 | The :class:`~magic.Magic` class supports `context managers 29 | `_, meaning 30 | it can be used with the ``with`` statement. Using the ``with`` statement is the 31 | recommended usage as failing to call :meth:`~magic.Magic.close` will leak 32 | resources. See :ref:`usage` for guidance. 33 | 34 | .. module:: magic 35 | :noindex: 36 | 37 | .. class:: Magic([paths, flags]) 38 | 39 | Instances of this class provide access to *libmagics*'s file identification 40 | capabilities. Multiple instances may exist, each instance is independant 41 | from the others. 42 | 43 | To supply a custom list of magic database files instead of letting libmagic 44 | search the default paths, supply a list of filenames using the paths 45 | argument. These filenames may be unicode string as described in 46 | :ref:`unicode`. 47 | 48 | By default *flags* is :data:`magic.MAGIC_MIME_TYPE` which requests default 49 | behaviour from *libmagic*. This behaviour can be controlled by passing 50 | alternative :ref:`constants` for flags. 51 | 52 | .. method:: id_filename(filename) 53 | 54 | Identify a file from a given filename. The file will be opened by 55 | *libmagic*, reading sufficient contents to complete the identification. 56 | 57 | .. method:: id_buffer(buffer) 58 | 59 | Identify a file from the contents of a string or buffer. 60 | 61 | .. method:: close() 62 | 63 | Release any resources held by *libmagic*. This will be called 64 | automatically when a context manager exists. 65 | 66 | .. method:: list() 67 | 68 | Prints a list of magic entries to standard out. There is no return 69 | value. It's mostly intended for debugging. 70 | 71 | .. attribute:: consistent 72 | 73 | This property will be ``True`` if the magic database files loaded by 74 | libmagic are consistent. 75 | 76 | This class encapsulates the low level ctypes api from :mod:`magic.api` that 77 | interfaces directly with *libmagic*. It's not expected that the user would want 78 | to do this. 79 | 80 | If you do not know if *libmagic* is available, refer to the :ref:`installation` 81 | section of the guide. 82 | 83 | .. _constants: 84 | 85 | Constants 86 | --------- 87 | 88 | .. module:: magic 89 | :noindex: 90 | 91 | .. data:: MAGIC_NONE 92 | 93 | Default flag for :class:`magic.Magic` that requests default behaviour from 94 | *libmagic*. 95 | 96 | .. data:: MAGIC_MIME_TYPE 97 | 98 | Supply to :class:`magic.Magic` constructor to return mime type instead of 99 | textual description. 100 | 101 | .. data:: MAGIC_MIME_ENCODING 102 | 103 | Supply to :class:`magic.Magic` constructor to return mime encoding instead 104 | of textual description. 105 | -------------------------------------------------------------------------------- /docs/source/command.rst: -------------------------------------------------------------------------------- 1 | Command Line Invocation 2 | ======================= 3 | 4 | *filemagic* can be invoked from the command line by running the 5 | :mod:`magic.command` module as a script. Pass ``-h`` or ``--help`` to print 6 | usage information. :: 7 | 8 | $ python -m magic.command --help 9 | Usage: python -m magic [options] file ... 10 | 11 | Options: 12 | -h, --help show this help message and exit 13 | -m PATHS, --magic=PATHS 14 | A colon separated list of magic files to use 15 | --json Format output in JSON 16 | 17 | One or more files can be passed to be identified. The textual description, 18 | mimetype and encoding type will be printed beneath each file's name.:: 19 | 20 | $ python -m magic.command setup.py 21 | setup.py 22 | Python script, ASCII text executable 23 | text/x-python 24 | us-ascii 25 | 26 | The output can also be rendered in machine parseable `JSON 27 | `_ instead of the simple textual description 28 | of above.. :: 29 | 30 | $ python -m magic.command --json setup.py 31 | { 32 | "setup.py": { 33 | "textual": "Python script, ASCII text executable", 34 | "mimetype": "text/x-python", 35 | "encoding": "us-ascii" 36 | } 37 | } 38 | 39 | The :mod:`magic.command` module is not intended to be a replacement for the 40 | *file* command. 41 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # filemagic documentation build configuration file, created by 5 | # sphinx-quickstart on Wed Mar 14 21:50:10 2012. 6 | # 7 | # This file is execfile()d with the current directory set to its containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys, os 16 | 17 | # If extensions (or modules to document with autodoc) are in another directory, 18 | # add these directories to sys.path here. If the directory is relative to the 19 | # documentation root, use os.path.abspath to make it absolute, like shown here. 20 | sys.path.insert(0, os.path.abspath('../..')) 21 | 22 | # -- General configuration ----------------------------------------------------- 23 | 24 | # If your documentation needs a minimal Sphinx version, state it here. 25 | #needs_sphinx = '1.0' 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be extensions 28 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 29 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.viewcode'] 30 | 31 | # Add any paths that contain templates here, relative to this directory. 32 | templates_path = ['_templates'] 33 | 34 | # The suffix of source filenames. 35 | source_suffix = '.rst' 36 | 37 | # The encoding of source files. 38 | #source_encoding = 'utf-8-sig' 39 | 40 | # The master toctree document. 41 | master_doc = 'index' 42 | 43 | # General information about the project. 44 | project = 'filemagic' 45 | copyright = '2012-2013, Aaron Iles' 46 | 47 | # The version info for the project you're documenting, acts as replacement for 48 | # |version| and |release|, also used in various other places throughout the 49 | # built documents. 50 | # 51 | # The short X.Y version. 52 | from magic import __version__ 53 | version = '.'.join(__version__.split('.')[:2]) 54 | # The full version, including alpha/beta/rc tags. 55 | release = __version__ 56 | 57 | # The language for content autogenerated by Sphinx. Refer to documentation 58 | # for a list of supported languages. 59 | #language = None 60 | 61 | # There are two options for replacing |today|: either, you set today to some 62 | # non-false value, then it is used: 63 | #today = '' 64 | # Else, today_fmt is used as the format for a strftime call. 65 | #today_fmt = '%B %d, %Y' 66 | 67 | # List of patterns, relative to source directory, that match files and 68 | # directories to ignore when looking for source files. 69 | exclude_patterns = [] 70 | 71 | # The reST default role (used for this markup: `text`) to use for all documents. 72 | #default_role = None 73 | 74 | # If true, '()' will be appended to :func: etc. cross-reference text. 75 | #add_function_parentheses = True 76 | 77 | # If true, the current module name will be prepended to all description 78 | # unit titles (such as .. function::). 79 | #add_module_names = True 80 | 81 | # If true, sectionauthor and moduleauthor directives will be shown in the 82 | # output. They are ignored by default. 83 | #show_authors = False 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = 'sphinx' 87 | 88 | # A list of ignored prefixes for module index sorting. 89 | #modindex_common_prefix = [] 90 | 91 | 92 | # -- Options for HTML output --------------------------------------------------- 93 | 94 | # The theme to use for HTML and HTML Help pages. See the documentation for 95 | # a list of builtin themes. 96 | html_theme = 'agogo' 97 | 98 | # Theme options are theme-specific and customize the look and feel of a theme 99 | # further. For a list of options available for each theme, see the 100 | # documentation. 101 | #html_theme_options = {} 102 | 103 | # Add any paths that contain custom themes here, relative to this directory. 104 | #html_theme_path = [] 105 | 106 | # The name for this set of Sphinx documents. If None, it defaults to 107 | # " v documentation". 108 | #html_title = None 109 | 110 | # A shorter title for the navigation bar. Default is the same as html_title. 111 | #html_short_title = None 112 | 113 | # The name of an image file (relative to this directory) to place at the top 114 | # of the sidebar. 115 | #html_logo = None 116 | 117 | # The name of an image file (within the static path) to use as favicon of the 118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 119 | # pixels large. 120 | #html_favicon = None 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = [] 126 | 127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 128 | # using the given strftime format. 129 | #html_last_updated_fmt = '%b %d, %Y' 130 | 131 | # If true, SmartyPants will be used to convert quotes and dashes to 132 | # typographically correct entities. 133 | #html_use_smartypants = True 134 | 135 | # Custom sidebar templates, maps document names to template names. 136 | #html_sidebars = {} 137 | 138 | # Additional templates that should be rendered to pages, maps page names to 139 | # template names. 140 | #html_additional_pages = {} 141 | 142 | # If false, no module index is generated. 143 | #html_domain_indices = True 144 | 145 | # If false, no index is generated. 146 | #html_use_index = True 147 | 148 | # If true, the index is split into individual pages for each letter. 149 | #html_split_index = False 150 | 151 | # If true, links to the reST sources are added to the pages. 152 | #html_show_sourcelink = True 153 | 154 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 155 | #html_show_sphinx = True 156 | 157 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 158 | #html_show_copyright = True 159 | 160 | # If true, an OpenSearch description file will be output, and all pages will 161 | # contain a tag referring to it. The value of this option must be the 162 | # base URL from which the finished HTML is served. 163 | #html_use_opensearch = '' 164 | 165 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 166 | #html_file_suffix = None 167 | 168 | # Output file base name for HTML help builder. 169 | htmlhelp_basename = 'filemagicdoc' 170 | 171 | 172 | # -- Options for LaTeX output -------------------------------------------------- 173 | 174 | latex_elements = { 175 | # The paper size ('letterpaper' or 'a4paper'). 176 | #'papersize': 'letterpaper', 177 | 178 | # The font size ('10pt', '11pt' or '12pt'). 179 | #'pointsize': '10pt', 180 | 181 | # Additional stuff for the LaTeX preamble. 182 | #'preamble': '', 183 | } 184 | 185 | # Grouping the document tree into LaTeX files. List of tuples 186 | # (source start file, target name, title, author, documentclass [howto/manual]). 187 | latex_documents = [ 188 | ('index', 'filemagic.tex', 'filemagic Documentation', 189 | 'Aaron Iles', 'manual'), 190 | ] 191 | 192 | # The name of an image file (relative to this directory) to place at the top of 193 | # the title page. 194 | #latex_logo = None 195 | 196 | # For "manual" documents, if this is true, then toplevel headings are parts, 197 | # not chapters. 198 | #latex_use_parts = False 199 | 200 | # If true, show page references after internal links. 201 | #latex_show_pagerefs = False 202 | 203 | # If true, show URL addresses after external links. 204 | #latex_show_urls = False 205 | 206 | # Documents to append as an appendix to all manuals. 207 | #latex_appendices = [] 208 | 209 | # If false, no module index is generated. 210 | #latex_domain_indices = True 211 | 212 | 213 | # -- Options for manual page output -------------------------------------------- 214 | 215 | # One entry per manual page. List of tuples 216 | # (source start file, name, description, authors, manual section). 217 | man_pages = [ 218 | ('index', 'filemagic', 'filemagic Documentation', 219 | ['Aaron Iles'], 1) 220 | ] 221 | 222 | # If true, show URL addresses after external links. 223 | #man_show_urls = False 224 | 225 | 226 | # -- Options for Texinfo output ------------------------------------------------ 227 | 228 | # Grouping the document tree into Texinfo files. List of tuples 229 | # (source start file, target name, title, author, 230 | # dir menu entry, description, category) 231 | texinfo_documents = [ 232 | ('index', 'filemagic', 'filemagic Documentation', 233 | 'Aaron Iles', 'filemagic', 'One line description of project.', 234 | 'Miscellaneous'), 235 | ] 236 | 237 | # Documents to append as an appendix to all manuals. 238 | #texinfo_appendices = [] 239 | 240 | # If false, no module index is generated. 241 | #texinfo_domain_indices = True 242 | 243 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 244 | #texinfo_show_urls = 'footnote' 245 | -------------------------------------------------------------------------------- /docs/source/guide.rst: -------------------------------------------------------------------------------- 1 | Guide to using filemagic 2 | ======================== 3 | 4 | .. include:: guide_content 5 | -------------------------------------------------------------------------------- /docs/source/guide_content: -------------------------------------------------------------------------------- 1 | .. _background: 2 | 3 | Background 4 | ---------- 5 | 6 | `libmagic `_ is the library that commonly 7 | supports the *file* command on Unix system, other than Max OSX which has its 8 | own implementation. The library handles the loading of *database* files that 9 | describe the magic numbers used to identify various file types, as well as the 10 | associated mime types. The library also handles character set detections. 11 | 12 | .. _installation: 13 | 14 | Installation 15 | ------------ 16 | 17 | Before installing *filemagic*, the *libmagic* library will need to be 18 | availabile. To test this is the check for the presence of the *file* command 19 | and/or the *libmagic* man page. :: 20 | 21 | $ which file 22 | $ man libmagic 23 | 24 | On Mac OSX, Apple has implemented their own version of the file command. 25 | However, *libmagic* can be installed using `homebrew 26 | `_ :: 27 | 28 | $ brew install libmagic 29 | 30 | After *brew* finished installing, the test for the *libmagic* man page should 31 | pass. 32 | 33 | Now that the presence of *libmagic* has been confirmed, use `pip 34 | `_ to install filemagic. :: 35 | 36 | $ pip install filemagic 37 | 38 | The :mod:`magic` module should now be availabe from the Python shell. :: 39 | 40 | >>> import magic 41 | 42 | The next section will describe how to use the :class:`magic.Magic` class to 43 | identify file types. 44 | 45 | .. _usage: 46 | 47 | Usage 48 | ----- 49 | 50 | The :mod:`magic` module uses `ctypes 51 | `_ to wrap the primitives from 52 | *libmagic* in the more user friendly :class:`magic.Magic` class. This class 53 | handles initialization, loading databases and the release of resources. :: 54 | 55 | >>> import magic 56 | 57 | To ensure that resources are correctly released by :class:`magic.Magic`, it's 58 | necessary to either explicitly call :meth:`~magic.Magic.close` on instances, 59 | or use ``with`` statement. :: 60 | 61 | >>> with magic.Magic() as m: 62 | ... pass 63 | ... 64 | 65 | :class:`magic.Magic` supports context managers which ensures resources are 66 | correctly released at the end of the ``with`` statements irrespective of any 67 | exceptions. 68 | 69 | To identify a file from it's filename, use the 70 | :meth:`~magic.Magic.id_filename()` method. :: 71 | 72 | >>> with magic.Magic() as m: 73 | ... m.id_filename('setup.py') 74 | ... 75 | 'Python script, ASCII text executable' 76 | 77 | Similarily to identify a file from a string that has already been read, use the 78 | :meth:`~magic.Magic.id_buffer` method. :: 79 | 80 | >>> with magic.Magic() as m: 81 | ... m.id_buffer('#!/usr/bin/python\n') 82 | ... 83 | 'Python script, ASCII text executable' 84 | 85 | To identify with mime type, rather than a textual description, pass the 86 | :data:`~magic.MAGIC_MIME_TYPE` flag when creating the :class:`magic.Magic` 87 | instance. :: 88 | 89 | >>> with magic.Magic(flags=magic.MAGIC_MIME_TYPE) as m: 90 | ... m.id_filename('setup.py') 91 | ... 92 | 'text/x-python' 93 | 94 | Similarily, :data:`~magic.MAGIC_MIME_ENCODING` can be passed to return the 95 | encoding type. :: 96 | 97 | >>> with magic.Magic(flags=magic.MAGIC_MIME_ENCODING) as m: 98 | ... m.id_filename('setup.py') 99 | ... 100 | 'us-ascii' 101 | 102 | .. _unicode: 103 | 104 | Memory management 105 | ----------------- 106 | 107 | The *libmagic* library allocates memory for its own use outside that Python. 108 | This memory needs to be released when a :class:`magic.Magic` instance is no 109 | longer needed. The preferred way to doing this is to explicitly call the 110 | :meth:`~magic.Magic.close` method or use the ``with`` statement, as 111 | described above. 112 | 113 | Starting with version 1.4 :class:`magic.Magic` this memory will be 114 | automatically cleaned up when the instance is garbage collected. However, 115 | unlike CPython, some Python interpreters such as `PyPy `_, 116 | `Jython `_ and `IronPython `_ do 117 | not have deterministic garbage collection. Because of this, *filemagic* will 118 | issue a warning if it automatically cleans up resources. 119 | 120 | Unicode and filemagic 121 | --------------------- 122 | 123 | On both Python2 and Python3, :class:`magic.Magic`'s methods will encode any 124 | unicode objects (the default string type for Python3) to byte strings before 125 | being passed to *libmagic*. On Python3, returned strings will be decoded to 126 | unicode using the default encoding type. The user **should not** be concerned 127 | whether unicode or bytes are passed to :class:`magic.Magic` methods. However, 128 | the user **will** need to be aware that returned strings are always unicode on 129 | Python3 and byte strings on Python2. 130 | 131 | .. _issues: 132 | 133 | Reporting issues 134 | ---------------- 135 | 136 | The source code for *filemagic* is hosted on 137 | `Github `_. 138 | Problems can be reported using Github's 139 | `issues tracking `_ system. 140 | 141 | *filemagic* has been tested against *libmagic* 5.11. Continuous integration 142 | is provided by `Travis CI `_. The current build status 143 | is |build_status|. 144 | 145 | .. |build_status| image:: https://secure.travis-ci.org/aliles/filemagic.png?branch=master 146 | :target: http://travis-ci.org/#!/aliles/filemagic 147 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. filemagic documentation master file, created by 2 | sphinx-quickstart on Wed Mar 14 21:50:10 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Introducing filemagic 7 | ===================== 8 | 9 | *filemagic* provides a Python API for *libmagic*, the library behind Unix 10 | *file* command. It enables the Python developer to easilty test for file types 11 | from the extensive identification library that is shipped with *libmagic*. 12 | 13 | "Any sufficiently advanced technology is indistinguishable from magic." 14 | 15 | -- Arthur C. Clark, 1961 16 | 17 | .. The *file* command and *libmagic* have been maintained since August, 1987. It's predecessor dates back to Bell Labs UNIX from 1973. 18 | 19 | Features 20 | ======== 21 | 22 | * Simple, Python API. 23 | * Identifies named files or strings. 24 | * Return a textual description, mime type or mime encoding. 25 | * Provide custom magic files to customize file detection. 26 | * Support for both Python2 and Python3. 27 | * Support for both CPython and PyPy. 28 | 29 | Table of Contents 30 | ================= 31 | 32 | .. toctree:: 33 | :maxdepth: 2 34 | 35 | guide 36 | command 37 | api 38 | 39 | Issues 40 | ====== 41 | 42 | If you encounter problems, please refer to :ref:`issues` from the guide. 43 | -------------------------------------------------------------------------------- /magic/__init__.py: -------------------------------------------------------------------------------- 1 | """File type identification using libmagic. 2 | 3 | The magic.Magic class provides a high level API to the libmagic library. 4 | 5 | >>> import magic 6 | >>> with magic.Magic() as m: 7 | ... m.id_filename('setup.py') 8 | ... 9 | 'ASCII text' 10 | 11 | Instances of magic.Magic support the context manager protocol (the 'with' 12 | statement). If not used, the close() method must be called to free resources 13 | allocated by libmagic. 14 | 15 | See http://filemagic.readthedocs.org for detailed documentation. 16 | """ 17 | from magic.flags import * 18 | from magic.identify import Magic, MagicError 19 | from magic.version import __version__ 20 | -------------------------------------------------------------------------------- /magic/api.py: -------------------------------------------------------------------------------- 1 | """File type identification using libmagic. 2 | 3 | A ctypes Python wrapper for the libmagic library. 4 | 5 | See libmagic(3) for low level details. 6 | """ 7 | import ctypes.util 8 | import ctypes 9 | import platform 10 | import warnings 11 | 12 | libname = ctypes.util.find_library('magic') or ctypes.util.find_library('magic1') 13 | if not libname: 14 | if platform.system() == 'SunOS': 15 | libname = 'libmagic.so' 16 | warnings.warn("ctypes.util.find_library does not function as " 17 | "expected on Solaris; manually setting libname to {0}. " 18 | "If import fails, verify that libmagic is installed " 19 | "to a directory registered with crle. ".format(libname), 20 | ImportWarning) 21 | else: 22 | raise ImportError('Unable to find magic library') 23 | 24 | try: 25 | lib = ctypes.CDLL(libname) 26 | except Exception: 27 | raise ImportError('Loading {0} failed'.format(libname)) 28 | 29 | 30 | # magic_t type 31 | class Cookie(ctypes.Structure): 32 | "Magic data structure" 33 | 34 | c_cookie_p = ctypes.POINTER(Cookie) 35 | 36 | 37 | # error handling 38 | class MagicError(EnvironmentError): 39 | "Error occured inside libmagic" 40 | 41 | 42 | def errcheck_int(result, func, arguments): 43 | "Raise an error if return integer is less than 0" 44 | if result < 0: 45 | cookie = arguments[0] 46 | errno = magic_errno(cookie) 47 | error = magic_error(cookie) 48 | raise MagicError(errno, error) 49 | return result 50 | 51 | 52 | def errcheck_null(result, func, arguments): 53 | "Raise an error if the return pointer is NULL" 54 | if not result: 55 | cookie = arguments[0] 56 | errno = magic_errno(cookie) 57 | error = magic_error(cookie) 58 | raise MagicError(errno, error) 59 | return result 60 | 61 | # dynamically load library 62 | lib.magic_open.argtypes = [ctypes.c_int] 63 | lib.magic_open.restype = c_cookie_p 64 | lib.magic_open.err_check = errcheck_null 65 | magic_open = lib.magic_open 66 | 67 | lib.magic_close.argyptes = [c_cookie_p] 68 | lib.magic_close.restype = None 69 | magic_close = lib.magic_close 70 | 71 | lib.magic_error.argyptes = [c_cookie_p] 72 | lib.magic_error.restype = ctypes.c_char_p 73 | magic_error = lib.magic_error 74 | 75 | lib.magic_errno.argyptes = [c_cookie_p] 76 | lib.magic_errno.restype = ctypes.c_int 77 | magic_errno = lib.magic_errno 78 | 79 | lib.magic_file.argyptes = [c_cookie_p, ctypes.c_char_p] 80 | lib.magic_file.restype = ctypes.c_char_p 81 | lib.magic_file.errcheck = errcheck_null 82 | magic_file = lib.magic_file 83 | 84 | lib.magic_buffer.argyptes = [c_cookie_p, ctypes.c_void_p, ctypes.c_size_t] 85 | lib.magic_buffer.restype = ctypes.c_char_p 86 | lib.magic_buffer.errcheck = errcheck_null 87 | magic_buffer = lib.magic_buffer 88 | 89 | lib.magic_setflags.argyptes = [c_cookie_p, ctypes.c_int] 90 | lib.magic_setflags.restype = ctypes.c_int 91 | lib.magic_setflags.errcheck = errcheck_int 92 | magic_setflags = lib.magic_setflags 93 | 94 | lib.magic_check.argyptes = [c_cookie_p, ctypes.c_char_p] 95 | lib.magic_check.restype = ctypes.c_int 96 | lib.magic_check.errcheck = errcheck_int 97 | magic_check = lib.magic_check 98 | 99 | lib.magic_compile.argyptes = [c_cookie_p, ctypes.c_char_p] 100 | lib.magic_compile.restype = ctypes.c_int 101 | lib.magic_compile.errcheck = errcheck_int 102 | magic_compile = lib.magic_compile 103 | 104 | lib.magic_load.argyptes = [c_cookie_p, ctypes.c_char_p] 105 | lib.magic_load.restype = ctypes.c_int 106 | lib.magic_load.errcheck = errcheck_int 107 | magic_load = lib.magic_load 108 | 109 | try: 110 | lib.magic_list.argyptes = [c_cookie_p, ctypes.c_char_p] 111 | lib.magic_list.restype = ctypes.c_int 112 | lib.magic_list.errcheck = errcheck_int 113 | magic_list = lib.magic_list 114 | except AttributeError: 115 | pass 116 | -------------------------------------------------------------------------------- /magic/command.py: -------------------------------------------------------------------------------- 1 | """Command line applicatoin for filemagic. 2 | 3 | Simple application similar to the Unix file command. 4 | """ 5 | from optparse import OptionParser 6 | import json 7 | import os 8 | import sys 9 | 10 | from magic.flags import MAGIC_NONE, MAGIC_MIME_TYPE, MAGIC_MIME_ENCODING 11 | from magic.identify import Magic 12 | 13 | try: 14 | from collections import OrderedDict as odict 15 | except ImportError: 16 | odict = dict 17 | 18 | SEPARATORS = (', ', ': ') 19 | 20 | 21 | class Identifiers(object): 22 | "Aggregate identifier for textual, mimetype and encoding identificaiton" 23 | 24 | def __init__(self, path): 25 | "Initialise all identifiers to None" 26 | self.path = None if not path else path.split(':') 27 | self.textual = None 28 | self.mimetype = None 29 | self.encoding = None 30 | 31 | def __enter__(self): 32 | "__enter__() -> self." 33 | self.textual = Magic(paths=self.path, flags=MAGIC_NONE) 34 | self.mimetype = Magic(paths=self.path, flags=MAGIC_MIME_TYPE) 35 | self.encoding = Magic(paths=self.path, flags=MAGIC_MIME_ENCODING) 36 | return self 37 | 38 | def __exit__(self, exc_type, exc_value, traceback): 39 | "__exit__(*excinfo) -> None. Closes libmagic resources." 40 | if self.textual is not None: 41 | self.textual.close() 42 | if self.mimetype is not None: 43 | self.mimetype.close() 44 | if self.encoding is not None: 45 | self.encoding.close() 46 | 47 | def id_filename(self, filename): 48 | identity = odict() 49 | identity['textual'] = self.textual.id_filename(filename) 50 | identity['mimetype'] = self.mimetype.id_filename(filename) 51 | identity['encoding'] = self.encoding.id_filename(filename) 52 | return identity 53 | 54 | 55 | def parse_command_line(arguments): 56 | "Parse command line arguments" 57 | usage = "usage: python -m magic [options] file ..." 58 | parser = OptionParser(usage=usage) 59 | parser.add_option("-m", "--magic", dest="paths", 60 | help="A colon separated list of magic files to use") 61 | parser.add_option("--json", action="store_true", default=False, 62 | help="Format output in JSON") 63 | return parser.parse_args(arguments) 64 | 65 | 66 | def print_json(filenames, results, fdesc=sys.stdout): 67 | "Print JSON formated results to file like object" 68 | json.dump(results, fdesc, indent=2, separators=SEPARATORS) 69 | fdesc.write(os.linesep) 70 | 71 | 72 | def print_text(filenames, results, fdesc=sys.stdout): 73 | "Print human readable results to file like object" 74 | template = "{filename}\n\t{textual}\n\t{mimetype}\n\t{encoding}\n" 75 | for name in filenames: 76 | result = results[name] 77 | fdesc.write(template.format(filename=name, **result)) 78 | 79 | 80 | def run(arguments=None): 81 | "Main loop for file like command using filemagic" 82 | arguments = arguments if arguments is None else sys.argv 83 | options, filenames = parse_command_line(arguments) 84 | results = odict() 85 | with Identifiers(options.paths) as identify: 86 | for name in filenames: 87 | results[name] = identify.id_filename(name) 88 | if options.json: 89 | print_json(filenames, results) 90 | else: 91 | print_text(filenames, results) 92 | 93 | 94 | if __name__ == "__main__": 95 | run() 96 | -------------------------------------------------------------------------------- /magic/compatability.py: -------------------------------------------------------------------------------- 1 | """Compatability wrapper between str and unicode.""" 2 | import sys 3 | from functools import wraps 4 | 5 | if sys.version_info[0] >= 3: 6 | unicode_t = str 7 | bytes_t = bytes 8 | decode_result = True 9 | else: 10 | unicode_t = unicode 11 | bytes_t = str 12 | decode_result = False 13 | 14 | 15 | def byte_args(positions): 16 | """Ensure argument at given position is a byte string. 17 | 18 | Will encode a unicode string to byte string if necessary. 19 | """ 20 | def decorator(func): 21 | ordinals = set(positions) 22 | @wraps(func) 23 | def wrapper(*args, **kwargs): 24 | def encoder(args): 25 | for pos, arg in enumerate(args): 26 | if pos in ordinals and isinstance(arg, unicode_t): 27 | yield arg.encode() 28 | else: 29 | yield arg 30 | return func(*encoder(args), **kwargs) 31 | return wrapper 32 | return decorator 33 | 34 | 35 | def iter_encode(iterable): 36 | """Iterate over sequence encoding all unicode elements. 37 | 38 | Non-unicode elements are yields unchanged. 39 | """ 40 | for item in iterable: 41 | if isinstance(item, unicode_t): 42 | item = item.encode() 43 | yield item 44 | 45 | 46 | def str_return(func): 47 | """Decode return result to unicode on Python3. 48 | 49 | Does nothing on Python 2. 50 | """ 51 | if not decode_result: 52 | return func 53 | @wraps(func) 54 | def wrapper(*args, **kwargs): 55 | value = func(*args, **kwargs) 56 | return value.decode() 57 | return wrapper 58 | -------------------------------------------------------------------------------- /magic/flags.py: -------------------------------------------------------------------------------- 1 | """File type identification using libmagic. 2 | 3 | Constants exported by magic.h header file. 4 | 5 | See libmagic(3) for low level details. 6 | """ 7 | # magic.h constants 8 | 9 | MAGIC_NONE = 0x000000 10 | MAGIC_DEBUG = 0x000001 11 | MAGIC_SYMLINK = 0x000002 12 | MAGIC_COMPRESS = 0x000004 13 | MAGIC_DEVICES = 0x000008 14 | MAGIC_MIME_TYPE = 0x000010 15 | MAGIC_CONTINUE = 0x000020 16 | MAGIC_CHECK = 0x000040 17 | MAGIC_PRESERVE_ATIME = 0x000080 18 | MAGIC_RAW = 0x000100 19 | MAGIC_ERROR = 0x000200 20 | MAGIC_MIME_ENCODING = 0x000400 21 | MAGIC_MIME = MAGIC_MIME_TYPE | MAGIC_MIME_ENCODING 22 | MAGIC_APPLE = 0x000800 23 | 24 | MAGIC_NO_CHECK_COMPRESS = 0x001000 25 | MAGIC_NO_CHECK_TAR = 0x002000 26 | MAGIC_NO_CHECK_SOFT = 0x004000 27 | MAGIC_NO_CHECK_APPTYPE = 0x008000 28 | MAGIC_NO_CHECK_ELF = 0x010000 29 | MAGIC_NO_CHECK_TEXT = 0x020000 30 | MAGIC_NO_CHECK_CDF = 0x040000 31 | MAGIC_NO_CHECK_TOKENS = 0x100000 32 | MAGIC_NO_CHECK_ENCODING = 0x200000 33 | 34 | MAGIC_NO_CHECK_BUILTIN = ( 35 | MAGIC_NO_CHECK_COMPRESS | 36 | MAGIC_NO_CHECK_TAR | 37 | MAGIC_NO_CHECK_APPTYPE | 38 | MAGIC_NO_CHECK_ELF | 39 | MAGIC_NO_CHECK_TEXT | 40 | MAGIC_NO_CHECK_CDF | 41 | MAGIC_NO_CHECK_TOKENS | 42 | MAGIC_NO_CHECK_ENCODING 43 | ) 44 | 45 | MAGIC_NO_CHECK_ASCII = MAGIC_NO_CHECK_TEXT 46 | 47 | MAGIC_NO_CHECK_FORTRAN = 0x000000 48 | MAGIC_NO_CHECK_TROFF = 0x000000 49 | -------------------------------------------------------------------------------- /magic/identify.py: -------------------------------------------------------------------------------- 1 | """File type identification using libmagic. 2 | 3 | A ctypes Python wrapper for libmagic library. 4 | 5 | See libmagic(3) for low level details. 6 | """ 7 | from functools import wraps 8 | import warnings 9 | import weakref 10 | 11 | try: 12 | from builtins import ResourceWarning as CleanupWarning 13 | except ImportError: 14 | from exceptions import RuntimeWarning as CleanupWarning 15 | 16 | from magic import api 17 | from magic.api import MagicError 18 | from magic.flags import MAGIC_NONE 19 | from magic.compatability import byte_args, iter_encode, str_return 20 | 21 | 22 | def raise_if_none(attrname, exception, message): 23 | "Raise an exception if the instance attribute is None." 24 | def decorator(func): 25 | @wraps(func) 26 | def wrapper(self, *args, **kwargs): 27 | if getattr(self, attrname) is None: 28 | raise exception(message) 29 | return func(self, *args, **kwargs) 30 | return wrapper 31 | return decorator 32 | 33 | 34 | class Magic(object): 35 | """Identify and describe files using libmagic magic numbers. 36 | 37 | Manages the resources for libmagic. Provides two methods for identifying 38 | file contents. 39 | 40 | - id_buffer, identifies the contents of the buffer 41 | - id_filename, identifies the contents of the named file 42 | 43 | To get mime types rather than textual descriptions, pass the flag 44 | MAGIC_MIME_TYPE to the contructor. To get the encoding pass 45 | MAGIC_MIME_ENCODING. 46 | """ 47 | 48 | def __init__(self, paths=None, flags=MAGIC_NONE): 49 | """Open and initialise resources from libmagic. 50 | 51 | ``paths`` is a list of magic database files to load. If None, the 52 | default database will be loaded. For details on the magic database file 53 | see magic(5). 54 | 55 | ``flags`` controls how libmagic should behave. See libmagic(3) for 56 | details of these flags. 57 | """ 58 | self._repr = "Magic(paths={0!r}, flags={1!r})".format(paths, flags) 59 | cookie = api.magic_open(flags) 60 | def cleanup(_): 61 | warnings.warn("magic.Magic() not closed before being garbage " 62 | "collected. Magic.close() should be called when finished" 63 | "with", 64 | CleanupWarning) 65 | api.magic_close(cookie) 66 | self.weakref = weakref.ref(self, cleanup) 67 | self.cookie = cookie 68 | pathstr = b':'.join(iter_encode(paths)) if paths else None 69 | try: 70 | api.magic_load(self.cookie, pathstr) 71 | except MagicError: 72 | self.close() 73 | raise 74 | 75 | def __enter__(self): 76 | "__enter__() -> self." 77 | return self 78 | 79 | def __exit__(self, exc_type, exc_value, traceback): 80 | "__exit__(*excinfo) -> None. Closes libmagic resources." 81 | self.close() 82 | 83 | def __repr__(self): 84 | "x.__repr__() <==> repr(x)" 85 | return self._repr 86 | 87 | def close(self): 88 | "Close any resources opened by libmagic" 89 | if self.cookie: 90 | api.magic_close(self.cookie) 91 | del self.weakref 92 | self.cookie = None 93 | 94 | @property 95 | @raise_if_none('cookie', MagicError, 'object has already been closed') 96 | def consistent(self): 97 | "True if magic database is consistent" 98 | return api.magic_check(self.cookie, None) >= 0 99 | 100 | @raise_if_none('cookie', MagicError, 'object has already been closed') 101 | @byte_args(positions=[1]) 102 | @str_return 103 | def id_buffer(self, buffer): 104 | "Return a textual description of the contents of buffer" 105 | return api.magic_buffer(self.cookie, 106 | api.ctypes.c_char_p(buffer), 107 | len(buffer)) 108 | 109 | @raise_if_none('cookie', MagicError, 'object has already been closed') 110 | @byte_args(positions=[1]) 111 | @str_return 112 | def id_filename(self, filename): 113 | "Return a textual description of the contents of the file" 114 | return api.magic_file(self.cookie, filename) 115 | 116 | @raise_if_none('cookie', MagicError, 'object has already been closed') 117 | def list(self, paths=None): 118 | "Print list of magic strings" 119 | pathstr = b':'.join(iter_encode(paths)) if paths else None 120 | try: 121 | api.magic_list(self.cookie, pathstr) 122 | except AttributeError: 123 | msg = 'list is not supported on this version of libmagic' 124 | raise MagicError(msg) 125 | -------------------------------------------------------------------------------- /magic/version.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.6" 2 | -------------------------------------------------------------------------------- /requirements/development.txt: -------------------------------------------------------------------------------- 1 | coverage 2 | coveralls 3 | flake8 4 | mock 5 | pip 6 | sphinx 7 | wheel 8 | -------------------------------------------------------------------------------- /requirements/production.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliles/filemagic/138649062f769fb10c256e454a3e94ecfbf3017b/requirements/production.txt -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | import re 4 | import sys 5 | 6 | 7 | def load_version(filename='magic/version.py'): 8 | "Parse a __version__ number from a source file" 9 | with open(filename) as source: 10 | text = source.read() 11 | match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", text) 12 | if not match: 13 | msg = "Unable to find version number in {}".format(filename) 14 | raise RuntimeError(msg) 15 | version = match.group(1) 16 | return version 17 | 18 | 19 | def load_rst(filename='docs/source/guide_content'): 20 | "Purge refs directives from restructured text" 21 | with open(filename) as source: 22 | text = source.read() 23 | doc = re.sub(r':\w+:`~?([a-zA-Z._()]+)`', r'*\1*', text) 24 | return doc 25 | 26 | PYTHON3K = sys.version_info[0] > 2 27 | 28 | setup( 29 | name="filemagic", 30 | version=load_version(), 31 | packages=['magic'], 32 | zip_safe=False, 33 | author="Aaron Iles", 34 | author_email="aaron.iles@gmail.com", 35 | url="http://filemagic.readthedocs.org", 36 | description="A Python API for libmagic, the library behind the Unix file command", 37 | long_description=load_rst(), 38 | license="ASL", 39 | classifiers=[ 40 | 'Development Status :: 5 - Production/Stable', 41 | 'Intended Audience :: Developers', 42 | 'License :: OSI Approved :: Apache Software License', 43 | 'Operating System :: OS Independent', 44 | 'Programming Language :: Python', 45 | 'Programming Language :: Python :: 2', 46 | 'Programming Language :: Python :: 2.6', 47 | 'Programming Language :: Python :: 2.7', 48 | 'Programming Language :: Python :: 3', 49 | 'Programming Language :: Python :: 3.2', 50 | 'Programming Language :: Python :: Implementation :: CPython', 51 | 'Programming Language :: Python :: Implementation :: PyPy', 52 | 'Topic :: Software Development :: Libraries :: Python Modules' 53 | ], 54 | tests_require=['mock'] + [] if PYTHON3K else ['unittest2'], 55 | test_suite="tests" if PYTHON3K else "unittest2.collector" 56 | ) 57 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliles/filemagic/138649062f769fb10c256e454a3e94ecfbf3017b/tests/__init__.py -------------------------------------------------------------------------------- /tests/magic/python: -------------------------------------------------------------------------------- 1 | 2 | #------------------------------------------------------------------------------ 3 | # $File: python,v 1.22 2013/03/18 12:49:55 christos Exp $ 4 | # python: file(1) magic for python 5 | # 6 | # Outlook puts """ too for urgent messages 7 | # From: David Necas 8 | # often the module starts with a multiline string 9 | 0 string/t """ Python script text executable 10 | # MAGIC as specified in Python/import.c (1.5 to 2.7a0 and 3.1a0, assuming 11 | # that Py_UnicodeFlag is off for Python 2) 12 | # 20121 ( YEAR - 1995 ) + MONTH + DAY (little endian followed by "\r\n" 13 | 0 belong 0x994e0d0a python 1.5/1.6 byte-compiled 14 | 0 belong 0x87c60d0a python 2.0 byte-compiled 15 | 0 belong 0x2aeb0d0a python 2.1 byte-compiled 16 | 0 belong 0x2ded0d0a python 2.2 byte-compiled 17 | 0 belong 0x3bf20d0a python 2.3 byte-compiled 18 | 0 belong 0x6df20d0a python 2.4 byte-compiled 19 | 0 belong 0xb3f20d0a python 2.5 byte-compiled 20 | 0 belong 0xd1f20d0a python 2.6 byte-compiled 21 | 0 belong 0x03f30d0a python 2.7 byte-compiled 22 | 0 belong 0x3b0c0d0a python 3.0 byte-compiled 23 | 0 belong 0x4f0c0d0a python 3.1 byte-compiled 24 | 0 belong 0x6c0c0d0a python 3.2 byte-compiled 25 | 0 belong 0x9e0c0d0a python 3.3 byte-compiled 26 | 27 | 0 search/1/w #!\ /usr/bin/python Python script text executable 28 | !:mime text/x-python 29 | 0 search/1/w #!\ /usr/local/bin/python Python script text executable 30 | !:mime text/x-python 31 | 0 search/1 #!/usr/bin/env\ python Python script text executable 32 | !:mime text/x-python 33 | 0 search/1 #!\ /usr/bin/env\ python Python script text executable 34 | !:mime text/x-python 35 | 36 | 37 | # from module.submodule import func1, func2 38 | 0 regex \^from\\s+(\\w|\\.)+\\s+import.*$ Python script text executable 39 | !:mime text/x-python 40 | 41 | # def __init__ (self, ...): 42 | 0 search/4096 def\ __init__ 43 | >&0 search/64 self Python script text executable 44 | !:mime text/x-python 45 | 46 | # comments 47 | 0 search/4096 ''' 48 | >&0 regex .*'''$ Python script text executable 49 | !:mime text/x-python 50 | 51 | 0 search/4096 """ 52 | >&0 regex .*"""$ Python script text executable 53 | !:mime text/x-python 54 | 55 | # try: 56 | # except: or finally: 57 | # block 58 | 0 search/4096 try: 59 | >&0 regex \^\\s*except.*: Python script text executable 60 | !:mime text/x-python 61 | >&0 search/4096 finally: Python script text executable 62 | !:mime text/x-python 63 | 64 | # def name(args, args): 65 | 0 regex \^(\ |\\t)*def\ +[a-zA-Z]+ 66 | >&0 regex \ *\\(([a-zA-Z]|,|\ )*\\):$ Python script text executable 67 | !:mime text/x-python 68 | -------------------------------------------------------------------------------- /tests/test_magic.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import mock 3 | import warnings 4 | import sys 5 | 6 | try: 7 | import unittest2 as unittest 8 | except ImportError: 9 | import unittest 10 | 11 | import magic 12 | 13 | 14 | class TestMagic(unittest.TestCase): 15 | 16 | def test_has_version(self): 17 | self.assertTrue(magic.__version__) 18 | 19 | def test_consistent_database(self): 20 | with magic.Magic() as m: 21 | self.assertTrue(m.consistent) 22 | 23 | def test_invalid_database(self): 24 | self.assertRaises(magic.MagicError, magic.Magic, 25 | paths=['test/magic/_false_']) 26 | 27 | def test_use_after_closed(self): 28 | with magic.Magic() as m: 29 | pass 30 | self.assertRaises(magic.MagicError, m.list, 'setup.py') 31 | 32 | def test_id_filename(self): 33 | with magic.Magic(paths=['tests/magic/python']) as m: 34 | id = m.id_filename('setup.py') 35 | self.assertTrue(id.startswith('Python script')) 36 | 37 | def test_id_buffer(self): 38 | with magic.Magic(paths=['tests/magic/python']) as m: 39 | id = m.id_buffer('#!/usr/bin/env python\n') 40 | self.assertTrue(id.startswith('Python script')) 41 | 42 | def test_mime_type_file(self): 43 | with magic.Magic(paths=['tests/magic/python'], 44 | flags=magic.MAGIC_MIME_TYPE) as m: 45 | id = m.id_filename('setup.py') 46 | self.assertEqual(id, 'text/x-python') 47 | 48 | def test_mime_type_desc(self): 49 | with magic.Magic(paths=['tests/magic/python'], 50 | flags=magic.MAGIC_MIME_TYPE) as m: 51 | id = m.id_buffer('#!/usr/bin/env python\n') 52 | self.assertEqual(id, 'text/x-python') 53 | 54 | def test_mime_encoding_file(self): 55 | with magic.Magic(paths=['tests/magic/python'], 56 | flags=magic.MAGIC_MIME_ENCODING) as m: 57 | id = m.id_filename('setup.py') 58 | self.assertEqual(id, 'us-ascii') 59 | 60 | def test_mime_encoding_desc(self): 61 | with magic.Magic(paths=['tests/magic/python'], 62 | flags=magic.MAGIC_MIME_ENCODING) as m: 63 | id = m.id_buffer('#!/usr/bin/env python\n') 64 | self.assertEqual(id, 'us-ascii') 65 | 66 | def test_repr(self): 67 | with magic.Magic(paths=['tests/magic/python'], 68 | flags=magic.MAGIC_MIME_ENCODING) as m: 69 | n = eval(repr(m), {'Magic': magic.Magic}) 70 | n.close() 71 | 72 | @unittest.skipIf(not hasattr(unittest.TestCase, 'assertWarns'), 73 | 'unittest does not support assertWarns') 74 | def test_resource_warning(self): 75 | with self.assertWarns(ResourceWarning): 76 | m = magic.Magic() 77 | del m 78 | 79 | @unittest.skipIf(hasattr(sys, 'pypy_version_info'), 80 | 'garbarge collection on PyPy is not deterministic') 81 | def test_weakref(self): 82 | magic_close = magic.api.magic_close 83 | with mock.patch('magic.api.magic_close') as close_mock: 84 | close_mock.side_effect = magic_close 85 | with warnings.catch_warnings(): 86 | warnings.simplefilter('ignore') 87 | m = magic.Magic() 88 | del m 89 | gc.collect() 90 | self.assertEqual(close_mock.call_count, 1) 91 | 92 | 93 | if __name__ == '__main__': 94 | unittest.main() 95 | --------------------------------------------------------------------------------