├── test ├── pylink ├── pyfile ├── perl.mgc ├── excel-example.xls ├── pyfile-compressed.gz ├── leaktest.rb ├── perl ├── mahoro.c └── filemagic_test.rb ├── lib ├── ruby-filemagic.rb ├── filemagic │ ├── version.rb │ └── ext.rb └── filemagic.rb ├── CONTRIBUTING.md ├── .gitignore ├── TODO ├── .travis.yml ├── ext └── filemagic │ ├── extconf.rb │ ├── filemagic.h │ └── filemagic.c ├── Dockerfile ├── Rakefile ├── ruby-filemagic.gemspec ├── ChangeLog └── README /test/pylink: -------------------------------------------------------------------------------- 1 | pyfile -------------------------------------------------------------------------------- /test/pyfile: -------------------------------------------------------------------------------- 1 | """ 2 | -------------------------------------------------------------------------------- /lib/ruby-filemagic.rb: -------------------------------------------------------------------------------- 1 | require 'filemagic' 2 | -------------------------------------------------------------------------------- /test/perl.mgc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackwinter/ruby-filemagic/HEAD/test/perl.mgc -------------------------------------------------------------------------------- /test/excel-example.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackwinter/ruby-filemagic/HEAD/test/excel-example.xls -------------------------------------------------------------------------------- /test/pyfile-compressed.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackwinter/ruby-filemagic/HEAD/test/pyfile-compressed.gz -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please note that this project is **no longer maintained**. I'm happy to hand over ownership to anyone who shows serious interest. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | doc.local 3 | pkg 4 | tmp 5 | .gh-pages 6 | ext/Makefile 7 | ext/filemagic.o 8 | ext/filemagic.so 9 | ext/mkmf.log 10 | lib/filemagic.so 11 | lib/**/*.mgc 12 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Find a better way to handle magic_load than just defaulting to NULL 2 | * Find a way to make magic_check work on NULL 3 | * Refactor code into initialize instead of new 4 | 5 | * Properly document C methods 6 | * Convert tests to RSpec 7 | * Add Rake tasks 8 | -------------------------------------------------------------------------------- /test/leaktest.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | require 'filemagic' 3 | 4 | warn "watch -n 1 'ps aux | grep [l]eaktest'" 5 | 6 | def do_file(filename) 7 | fm = FileMagic.new(0) 8 | file = fm.file(filename) 9 | fm.close 10 | return file 11 | end 12 | 13 | loop do 14 | puts do_file($0) 15 | sleep 1 16 | end 17 | -------------------------------------------------------------------------------- /lib/filemagic/version.rb: -------------------------------------------------------------------------------- 1 | class FileMagic 2 | 3 | module Version 4 | 5 | MAJOR = 0 6 | MINOR = 7 7 | TINY = 3 8 | 9 | class << self 10 | 11 | # Returns array representation. 12 | def to_a 13 | [MAJOR, MINOR, TINY] 14 | end 15 | 16 | # Short-cut for version string. 17 | def to_s 18 | to_a.join('.') 19 | end 20 | 21 | end 22 | 23 | end 24 | 25 | VERSION = Version.to_s 26 | 27 | end 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: 2 | ruby 3 | 4 | rvm: 5 | - '1.9' 6 | - '2.0' 7 | - '2.1' 8 | - '2.2' 9 | - '2.3.1' 10 | - ruby-head 11 | - rbx 12 | 13 | matrix: 14 | allow_failures: 15 | - rvm: ruby-head 16 | - rvm: rbx 17 | 18 | fast_finish: 19 | true 20 | 21 | script: 22 | rake test 23 | 24 | install: 25 | - gem install nuggets power_assert 26 | - gem install --prerelease --development ruby-filemagic 27 | 28 | addons: 29 | apt_packages: 30 | libmagic-dev 31 | 32 | sudo: 33 | false 34 | -------------------------------------------------------------------------------- /test/perl: -------------------------------------------------------------------------------- 1 | 2 | #------------------------------------------------------------------------------ 3 | # perl: file(1) magic for Larry Wall's perl language. 4 | # 5 | # The ``eval'' line recognizes an outrageously clever hack for USG systems. 6 | # Keith Waclena 7 | # Send additions to 8 | 0 string/b #!\ /bin/perl perl script text executable 9 | 0 string eval\ "exec\ /bin/perl perl script text 10 | 0 string/b #!\ /usr/bin/perl perl script text executable 11 | 0 string eval\ "exec\ /usr/bin/perl perl script text 12 | 0 string/b #!\ /usr/local/bin/perl perl script text 13 | 0 string eval\ "exec\ /usr/local/bin/perl perl script text executable 14 | 0 string eval\ '(exit\ $?0)'\ &&\ eval\ 'exec perl script text 15 | 16 | # a couple more, by me 17 | # XXX: christos matches 18 | #0 regex package Perl5 module source text (via regex) 19 | 0 string package Perl5 module source text 20 | -------------------------------------------------------------------------------- /ext/filemagic/extconf.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | 3 | HEADER_DIRS = [ 4 | '/opt/local/include', # MacPorts 5 | '/usr/local/include', # compiled from source and Homebrew 6 | '/opt/homebrew/include', # compiled from source and Homebrew (ARM based Macs) 7 | '/usr/include', # system 8 | ] 9 | 10 | LIB_DIRS = [ 11 | '/opt/local/lib', # MacPorts 12 | '/usr/local/lib', # compiled from source and Homebrew 13 | '/opt/homebrew/lib', # compiled from source and Homebrew (ARM based Macs) 14 | '/usr/lib', # system 15 | ] 16 | 17 | $CFLAGS << ' -Wall' if ENV['WALL'] 18 | $LDFLAGS << ' -static-libgcc' if RUBY_PLATFORM =~ /cygwin|mingw|mswin/ 19 | 20 | dir_config('magic', HEADER_DIRS, LIB_DIRS) 21 | dir_config('gnurx', HEADER_DIRS, LIB_DIRS) 22 | 23 | have_library('gnurx') 24 | 25 | if have_library('magic', 'magic_open') && have_header('magic.h') 26 | have_func('magic_version') 27 | have_header('file/patchlevel.h') 28 | create_makefile('filemagic/ruby_filemagic') 29 | else 30 | abort '*** ERROR: missing required library to compile this module' 31 | end 32 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.4 2 | 3 | RUN apt-get update && apt-get install -y mingw-w64 && gem install rake-compiler 4 | ENV HOST_ARCH i686-w64-mingw32 5 | 6 | ENV GNURX_VERSION 2.5.1 7 | ENV GNURX_SOURCE /mingw-libgnurx-$GNURX_VERSION 8 | RUN curl https://vorboss.dl.sourceforge.net/project/mingw/Other/UserContributed/regex/mingw-regex-$GNURX_VERSION/mingw-libgnurx-$GNURX_VERSION-src.tar.gz | \ 9 | tar xzvf - && \ 10 | cd $GNURX_SOURCE && \ 11 | ./configure --host=$HOST_ARCH --enable-static --disable-shared && \ 12 | make 13 | 14 | ENV MAGIC_VERSION 5.31 15 | ENV MAGIC_SOURCE /file-$MAGIC_VERSION 16 | RUN curl ftp://ftp.astron.com/pub/file/file-$MAGIC_VERSION.tar.gz | \ 17 | tar xzvf - && \ 18 | cd $MAGIC_SOURCE && \ 19 | ./configure --disable-silent-rules --enable-fsect-man5 && \ 20 | make && \ 21 | make clean && \ 22 | LDFLAGS=-L$GNURX_SOURCE CFLAGS=-I$GNURX_SOURCE ./configure --disable-silent-rules --enable-fsect-man5 --host=$HOST_ARCH && \ 23 | make || true 24 | 25 | ENV CROSS_RUBIES 2.3.4 2.4.1 26 | RUN for i in $CROSS_RUBIES; do rake-compiler cross-ruby VERSION=$i; done 27 | 28 | ENV APP_SOURCE /ruby-filemagic 29 | RUN mkdir $APP_SOURCE 30 | WORKDIR $APP_SOURCE 31 | 32 | RUN echo "source 'https://rubygems.org'\ngemspec\n" > Gemfile 33 | COPY *.gemspec . 34 | RUN bundle install 35 | 36 | COPY . . 37 | ENTRYPOINT rake gem:native WITH_CROSS_GNURX=$GNURX_SOURCE WITH_CROSS_MAGIC=$MAGIC_SOURCE 38 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require_relative 'lib/filemagic/version' 2 | 3 | begin 4 | require 'hen' 5 | 6 | file mgc = 'lib/filemagic/magic.mgc' do 7 | dir = ENV['WITH_CROSS_MAGIC'] 8 | 9 | dir && File.exist?(src = File.join(dir, 'magic', File.basename(mgc))) ? 10 | cp(src, mgc) : File.binwrite(mgc, "\x1C\x04\x1E\xF1\x0C".ljust(248, "\0")) 11 | end 12 | 13 | Hen.lay! {{ 14 | gem: { 15 | name: %q{ruby-filemagic}, 16 | version: FileMagic::VERSION, 17 | summary: 'Ruby bindings to the magic(4) library', 18 | authors: ['Travis Whitton', 'Jens Wille'], 19 | email: 'jens.wille@gmail.com', 20 | license: %q{Ruby}, 21 | homepage: :blackwinter, 22 | local_files: [mgc], 23 | extension: { 24 | with_cross_gnurx: lambda { |dir| [dir] }, 25 | with_cross_magic: lambda { |dir| [src = 26 | File.join(dir, 'src'), File.join(src, '.libs')] } 27 | }, 28 | 29 | required_ruby_version: '>= 1.9.3' 30 | } 31 | }} 32 | rescue LoadError => err 33 | warn "Please install the `hen' gem. (#{err})" 34 | end 35 | 36 | namespace :docker do 37 | 38 | name = "ruby-filemagic-gem-native:#{FileMagic::VERSION}" 39 | 40 | task :build do 41 | sh *%W[docker build -t #{name} .] 42 | end 43 | 44 | task :run do 45 | sh *%W[docker run -it --rm -v #{Dir.pwd}/pkg:/ruby-filemagic/pkg #{name}] 46 | end 47 | 48 | desc "Build native gems using docker image #{name}" 49 | task 'gem:native' => %w[build run] 50 | 51 | end 52 | -------------------------------------------------------------------------------- /lib/filemagic/ext.rb: -------------------------------------------------------------------------------- 1 | require 'filemagic' 2 | 3 | module FileMagic::Ext 4 | 5 | def self.included(base) 6 | base.class_eval { 7 | extend ClassMethods 8 | include InstanceMethods 9 | } 10 | end 11 | 12 | module ClassMethods 13 | 14 | def file_type(file, *flags) 15 | raise NotImplementedError, 'must be implemented by including class' 16 | end 17 | 18 | def file(file, *flags) 19 | file_type(file, *flags) 20 | end 21 | 22 | def mime_type(file, *flags) 23 | file_type(file, *flags.unshift(:mime)) 24 | end 25 | 26 | alias_method :mime, :mime_type 27 | 28 | def content_type(file, *flags) 29 | mime_type(file, *flags << { simplified: true }) 30 | end 31 | 32 | end 33 | 34 | module InstanceMethods 35 | 36 | def file_type(*flags) 37 | self.class.file_type(self, *flags) 38 | end 39 | 40 | alias_method :file, :file_type 41 | 42 | def mime_type(*flags) 43 | self.class.mime_type(self, *flags) 44 | end 45 | 46 | alias_method :mime, :mime_type 47 | 48 | def content_type(*flags) 49 | self.class.content_type(self, *flags) 50 | end 51 | 52 | end 53 | 54 | end 55 | 56 | class File 57 | 58 | include FileMagic::Ext 59 | 60 | def self.file_type(file, *flags) 61 | FileMagic.fm(*flags).file(file.respond_to?(:path) ? file.path : file) 62 | rescue FileMagic::FileMagicError 63 | end 64 | 65 | end 66 | 67 | class String 68 | 69 | include FileMagic::Ext 70 | 71 | def self.file_type(string, *flags) 72 | FileMagic.fm(*flags).buffer(string) 73 | rescue FileMagic::FileMagicError 74 | end 75 | 76 | end 77 | 78 | if $0 == __FILE__ 79 | f = __FILE__ 80 | p f 81 | 82 | p File.file_type(f) 83 | p File.mime_type(f) 84 | p File.content_type(f) 85 | 86 | f = File.new(f) 87 | p f 88 | 89 | p f.file_type 90 | p f.mime_type 91 | p f.content_type 92 | 93 | s = '#! /usr/bin/ruby' 94 | p s 95 | 96 | p s.file_type 97 | p s.mime_type 98 | p s.content_type 99 | end 100 | -------------------------------------------------------------------------------- /ruby-filemagic.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # stub: ruby-filemagic 0.7.3 ruby lib 3 | # stub: ext/filemagic/extconf.rb 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "ruby-filemagic".freeze 7 | s.version = "0.7.3" 8 | 9 | s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= 10 | s.require_paths = ["lib".freeze] 11 | s.authors = ["Travis Whitton".freeze, "Jens Wille".freeze] 12 | s.date = "2022-01-07" 13 | s.description = "Ruby bindings to the magic(4) library".freeze 14 | s.email = "jens.wille@gmail.com".freeze 15 | s.extensions = ["ext/filemagic/extconf.rb".freeze] 16 | s.extra_rdoc_files = ["README".freeze, "ChangeLog".freeze, "ext/filemagic/filemagic.c".freeze] 17 | s.files = ["CONTRIBUTING.md".freeze, "ChangeLog".freeze, "Dockerfile".freeze, "README".freeze, "Rakefile".freeze, "TODO".freeze, "ext/filemagic/extconf.rb".freeze, "ext/filemagic/filemagic.c".freeze, "ext/filemagic/filemagic.h".freeze, "lib/filemagic.rb".freeze, "lib/filemagic/ext.rb".freeze, "lib/filemagic/magic.mgc".freeze, "lib/filemagic/version.rb".freeze, "lib/ruby-filemagic.rb".freeze, "test/excel-example.xls".freeze, "test/filemagic_test.rb".freeze, "test/leaktest.rb".freeze, "test/mahoro.c".freeze, "test/perl".freeze, "test/perl.mgc".freeze, "test/pyfile".freeze, "test/pyfile-compressed.gz".freeze, "test/pylink".freeze] 18 | s.homepage = "http://github.com/blackwinter/ruby-filemagic".freeze 19 | s.licenses = ["Ruby".freeze] 20 | s.post_install_message = "\nruby-filemagic-0.7.3 [2022-01-07]:\n\n* Dockerfile to build native extension (pull request #26 by Pavel Lobashov).\n* Include paths for ARM-based Apple Macs (Apple Silicon) (pull request #35 by\n @545ch4).\n\n".freeze 21 | s.rdoc_options = ["--title".freeze, "ruby-filemagic Application documentation (v0.7.3)".freeze, "--charset".freeze, "UTF-8".freeze, "--line-numbers".freeze, "--all".freeze, "--main".freeze, "README".freeze] 22 | s.required_ruby_version = Gem::Requirement.new(">= 1.9.3".freeze) 23 | s.rubygems_version = "3.2.5".freeze 24 | s.summary = "Ruby bindings to the magic(4) library".freeze 25 | 26 | if s.respond_to? :specification_version then 27 | s.specification_version = 4 28 | end 29 | 30 | if s.respond_to? :add_runtime_dependency then 31 | s.add_development_dependency(%q.freeze, ["~> 0.9", ">= 0.9.1"]) 32 | s.add_development_dependency(%q.freeze, [">= 0"]) 33 | s.add_development_dependency(%q.freeze, [">= 0"]) 34 | s.add_development_dependency(%q.freeze, [">= 0"]) 35 | else 36 | s.add_dependency(%q.freeze, ["~> 0.9", ">= 0.9.1"]) 37 | s.add_dependency(%q.freeze, [">= 0"]) 38 | s.add_dependency(%q.freeze, [">= 0"]) 39 | s.add_dependency(%q.freeze, [">= 0"]) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ext/filemagic/filemagic.h: -------------------------------------------------------------------------------- 1 | #ifndef FILEMAGIC_H 2 | #define FILEMAGIC_H 3 | 4 | #include "ruby.h" 5 | #include 6 | #include 7 | #include 8 | #ifdef HAVE_FILE_PATCHLEVEL_H 9 | #include 10 | #endif 11 | 12 | #define GetMagicSet(obj, ms) {\ 13 | if (RTEST(rb_magic_closed_p(obj))) {\ 14 | rb_raise(rb_eRuntimeError, "closed stream");\ 15 | }\ 16 | else {\ 17 | Data_Get_Struct((obj), struct magic_set, (ms));\ 18 | }\ 19 | } 20 | 21 | #define RB_MAGIC_TYPE_FILE type = magic_file(ms, StringValuePtr(arg)); 22 | #define RB_MAGIC_TYPE_BUFFER { char *arg_str = StringValuePtr(arg); type = magic_buffer(ms, arg_str, RSTRING_LEN(arg)); } 23 | #define RB_MAGIC_TYPE_DESCRIPTOR type = magic_descriptor(ms, NUM2INT(arg)); 24 | 25 | #define RB_MAGIC_TYPE(what, WHAT) \ 26 | static VALUE \ 27 | rb_magic_##what(int argc, VALUE *argv, VALUE self) {\ 28 | VALUE arg, simple, res;\ 29 | const char *type;\ 30 | magic_t ms;\ 31 | \ 32 | rb_scan_args(argc, argv, "11", &arg, &simple);\ 33 | GetMagicSet(self, ms);\ 34 | \ 35 | RB_MAGIC_TYPE_##WHAT\ 36 | if (type == NULL) {\ 37 | rb_raise(rb_FileMagicError, "failed lookup: %s", magic_error(ms));\ 38 | }\ 39 | \ 40 | res = rb_str_new2(type);\ 41 | \ 42 | if (NIL_P(simple)) {\ 43 | simple = rb_attr_get(self, rb_intern("@simplified"));\ 44 | }\ 45 | \ 46 | if (RTEST(simple)) {\ 47 | rb_funcall(res, rb_intern("downcase!"), 0);\ 48 | \ 49 | return rb_funcall(res, rb_intern("slice"), 2,\ 50 | rb_const_get(cFileMagic, rb_intern("SIMPLE_RE")), INT2FIX(1));\ 51 | }\ 52 | else {\ 53 | return res;\ 54 | }\ 55 | } 56 | 57 | #define RB_MAGIC_APPRENTICE(what) \ 58 | static VALUE \ 59 | rb_magic_##what(int argc, VALUE *argv, VALUE self) {\ 60 | VALUE str;\ 61 | const char *file;\ 62 | magic_t ms;\ 63 | \ 64 | file = rb_scan_args(argc, argv, "01", &str) == 1 ? StringValuePtr(str) : NULL;\ 65 | \ 66 | GetMagicSet(self, ms);\ 67 | \ 68 | return magic_##what(ms, file) ? Qfalse : Qtrue;\ 69 | } 70 | 71 | #define RB_MAGIC_SET_VERSION(m, p) sprintf(version, "%d.%02d", m, p); 72 | 73 | static VALUE cFileMagic, rb_FileMagicError; 74 | 75 | static VALUE rb_magic_version(VALUE); 76 | static VALUE rb_magic_getpath(VALUE); 77 | static VALUE rb_magic_flags(VALUE, VALUE); 78 | 79 | static VALUE rb_magic_new(int, VALUE*, VALUE); 80 | static void rb_magic_free(magic_t); 81 | static VALUE rb_magic_init(int, VALUE*, VALUE); 82 | 83 | static VALUE rb_magic_close(VALUE); 84 | static VALUE rb_magic_closed_p(VALUE); 85 | 86 | static VALUE rb_magic_file(int, VALUE*, VALUE); 87 | static VALUE rb_magic_buffer(int, VALUE*, VALUE); 88 | static VALUE rb_magic_descriptor(int, VALUE*, VALUE); 89 | 90 | static VALUE rb_magic_getflags(VALUE); 91 | static VALUE rb_magic_setflags(VALUE, VALUE); 92 | 93 | static VALUE rb_magic_list(int, VALUE*, VALUE); 94 | static VALUE rb_magic_load(int, VALUE*, VALUE); 95 | static VALUE rb_magic_check(int, VALUE*, VALUE); 96 | static VALUE rb_magic_compile(int, VALUE*, VALUE); 97 | 98 | void Init_ruby_filemagic(void); 99 | 100 | #endif /* FILEMAGIC_H */ 101 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | # markup: rd 2 | 3 | = Revision history for ruby-filemagic 4 | 5 | == 0.7.3 [2022-01-07] 6 | 7 | * Dockerfile to build native extension (pull request #26 by Pavel Lobashov). 8 | * Include paths for ARM-based Apple Macs (Apple Silicon) (pull request #35 by 9 | @545ch4). 10 | 11 | == 0.7.2 [2017-07-02] 12 | 13 | * Fix segfault on buffer(nil) when compiled with GCC (pull request 14 | #24 by Yuya Tanaka). 15 | 16 | == 0.7.1 [2015-10-27] 17 | 18 | * List default lib and header directories (pull request #18 by Adam Wróbel). 19 | 20 | == 0.7.0 [2015-07-28] 21 | 22 | * Changed FileMagic#io to optionally rewind input afterwards. 23 | * New method FileMagic.library_version (+magic_version+). 24 | * New method FileMagic#descriptor (+magic_descriptor+). 25 | * New method FileMagic#fd. 26 | * Added new constants from file 5.23. 27 | 28 | == 0.6.3 [2015-02-06] 29 | 30 | * Fixed build error with Clang (issue #13 by Stan Carver II). 31 | 32 | == 0.6.2 [2015-02-06] 33 | 34 | * Improved Windows support (issue #10 by Matt Hoyle). 35 | * Fixed FileMagic.path to account for missing magic file. 36 | 37 | == 0.6.1 [2014-08-19] 38 | 39 | * Account for options passed to FileMagic.fm (issue #7 by Adam Wróbel). 40 | 41 | == 0.6.0 [2014-05-16] 42 | 43 | * Required Ruby version >= 1.9.3. 44 | * New method FileMagic#list (+magic_list+). 45 | * New method FileMagic#load (+magic_load+). 46 | * New method FileMagic.path (+magic_getpath+). 47 | * New method FileMagic.magic_version. 48 | * New method FileMagic.flags. 49 | * New flag +no_check_builtin+. 50 | * Tests print libmagic version and path. 51 | * Internal refactoring. 52 | 53 | == 0.5.2 [2014-04-24] 54 | 55 | * Use MAGIC_VERSION if available. 56 | * Made tests more robust. 57 | 58 | == 0.5.1 [2014-04-15] 59 | 60 | * New method FileMagic#io. 61 | * Housekeeping. 62 | 63 | == 0.5.0 [2013-12-19] 64 | 65 | * Removed usage of +RARRAY_PTR+ (cf. Ruby bug 66 | {#8399}[https://bugs.ruby-lang.org/issues/8399]). 67 | * Housekeeping. 68 | 69 | == 0.4.2 [2010-09-16] 70 | 71 | * Not all versions/distributions have file/patchlevel.h 72 | 73 | == 0.4.1 [2010-09-15] 74 | 75 | * Added MAGIC_VERSION 76 | * Fixed example script 77 | 78 | == 0.4.0 [2010-09-14] 79 | 80 | * Brushed up C layer 81 | * Moved most of the Ruby stuff to C 82 | * No longer expose internal state (@closed, @flags) 83 | * No longer expose internal C methods (fm_*) 84 | * Updated for magic(4) version 5.04 85 | 86 | == 0.3.0 [2010-09-10] 87 | 88 | * Ruby 1.9.2 compatibility (Martin Carpenter) 89 | * Exposed flags as symbols (Martin Carpenter) 90 | 91 | == 0.2.2 [2010-03-02] 92 | 93 | * Allow '.' when abbreviating mime types (Eric Schwartz) 94 | * Cleanup and project file fixes 95 | 96 | == 0.2.1 [2008-09-18] 97 | 98 | * Added mahoro source file and tests for reference and inspiration 99 | * We have a Rubyforge project now! :-) 100 | 101 | == 0.2.0 [2008-09-12] 102 | 103 | * Modified C API 104 | * Uniform C function prefix rb_magic_ (instead of magick_) 105 | * Uniform Ruby method prefix fm_ (instead of none) 106 | * More magic(4) constants (if available) 107 | * Added Ruby wrapper for more rubyish interface 108 | * Added extensions for File and String core classes 109 | * Added/updated project files 110 | * Now available as a Rubygem! 111 | 112 | == 0.1.1 [2003-07-30] 113 | 114 | * Added manual close method 115 | * Added unit test suite 116 | 117 | == 0.1.0 [2003-07-28] 118 | 119 | * Initial release. 120 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | = FileMagic Library Binding 2 | 3 | == VERSION 4 | 5 | This documentation refers to filemagic version 0.7.3 6 | 7 | 8 | == DESCRIPTION 9 | 10 | FileMagic extension module. See also libmagic(3), file(1) and magic(4). 11 | 12 | === Constants 13 | 14 | MAGIC_NONE:: No flags 15 | MAGIC_DEBUG:: Turn on debugging 16 | MAGIC_SYMLINK:: Follow symlinks 17 | MAGIC_COMPRESS:: Check inside compressed files 18 | MAGIC_DEVICES:: Look at the contents of devices 19 | MAGIC_MIME:: Return a mime string 20 | MAGIC_CONTINUE:: Return all matches, not just the first 21 | MAGIC_CHECK:: Print warnings to stderr 22 | 23 | === Methods 24 | 25 | file(filename):: Returns a textual description of the contents 26 | of the filename argument 27 | buffer(string):: Returns a textual description of the contents 28 | of the string argument 29 | check(filename):: Checks the validity of entries in the database 30 | file passed in as filename 31 | compile(filename):: Compiles the database file passed in as filename 32 | load(filename):: Loads the database file passed in as filename 33 | close():: Closes the magic database and frees any memory 34 | allocated 35 | 36 | === Synopsis 37 | 38 | require 'filemagic' 39 | 40 | p FileMagic::VERSION 41 | # => "0.7.3" 42 | p FileMagic::MAGIC_VERSION 43 | # => "5.39" 44 | 45 | p FileMagic.new.flags 46 | # => [] 47 | 48 | FileMagic.open(:mime) { |fm| 49 | p fm.flags 50 | # => [:mime_type, :mime_encoding] 51 | p fm.file(__FILE__) 52 | # => "text/plain; charset=us-ascii" 53 | p fm.file(__FILE__, true) 54 | # => "text/plain" 55 | 56 | fm.flags = [:raw, :continue] 57 | p fm.flags 58 | # => [:continue, :raw] 59 | } 60 | 61 | fm = FileMagic.new 62 | p fm.flags 63 | # => [] 64 | 65 | mime = FileMagic.mime 66 | p mime.flags 67 | # => [:mime_type, :mime_encoding] 68 | 69 | === Environment 70 | 71 | The environment variable +MAGIC+ can be used to set the default magic file name. 72 | 73 | === Installation 74 | 75 | Install the gem: 76 | 77 | sudo gem install ruby-filemagic 78 | 79 | The file(1) library and headers are required: 80 | 81 | Debian/Ubuntu:: +libmagic-dev+ 82 | Fedora/SuSE:: +file-devel+ 83 | Alpine:: libmagic file file-dev 84 | Gentoo:: +sys-libs/libmagic+ 85 | OS X:: brew install libmagic 86 | 87 | === Build native extension 88 | 89 | rake docker:gem:native 90 | 91 | Requires Docker[https://docker.com] to be installed. 92 | 93 | == LINKS 94 | 95 | Homepage:: https://www.darwinsys.com/file/ 96 | Documentation:: https://blackwinter.github.io/ruby-filemagic 97 | Source code:: https://github.com/blackwinter/ruby-filemagic 98 | RubyGem:: https://rubygems.org/gems/ruby-filemagic 99 | Travis CI:: https://travis-ci.org/blackwinter/ruby-filemagic 100 | 101 | 102 | == AUTHORS 103 | 104 | * Travis Whitton (Original author) 105 | * Jens Wille 106 | 107 | 108 | == CREDITS 109 | 110 | * Martin Carpenter for Ruby 1.9.2 compatibility 111 | and other improvements. 112 | 113 | * Pavel Lobashov (@ShockwaveNN) for Dockerfile to build cross-compiled Windows 114 | extension (pull request #26). 115 | 116 | 117 | == COPYING 118 | 119 | The filemagic extension library is copywrited free software by Travis Whitton 120 | . You can redistribute it under the terms specified in 121 | the COPYING file of the Ruby distribution. 122 | 123 | 124 | == WARRANTY 125 | 126 | THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 127 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 128 | WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR 129 | PURPOSE. 130 | -------------------------------------------------------------------------------- /test/mahoro.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is Public Domain. 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | struct MagicCookie 9 | { 10 | magic_t cookie; 11 | }; 12 | 13 | static VALUE cMahoro; 14 | static VALUE eMahoroError; 15 | 16 | static void 17 | mahoro_free(ptr) 18 | struct MagicCookie *ptr; 19 | { 20 | magic_close(ptr->cookie); 21 | free(ptr); 22 | } 23 | 24 | static VALUE 25 | mahoro_allocate(klass) 26 | VALUE klass; 27 | { 28 | return Data_Wrap_Struct(klass, 0, mahoro_free, 0); 29 | } 30 | 31 | static VALUE 32 | mahoro_initialize(argc, argv, self) 33 | int argc; 34 | VALUE *argv, self; 35 | { 36 | int flags = MAGIC_NONE; 37 | char *path = 0; 38 | struct MagicCookie *ptr; 39 | magic_t cookie; 40 | VALUE vpath, vflags; 41 | 42 | switch(rb_scan_args(argc, argv, "02", &vflags, &vpath)) { 43 | case 2: 44 | if(!NIL_P(vpath)) { 45 | path = StringValuePtr(vpath); 46 | } 47 | /* fallthrough */ 48 | case 1: 49 | flags = FIX2INT(vflags); 50 | break; 51 | } 52 | 53 | if(!(cookie = magic_open(flags))) { 54 | rb_raise(eMahoroError, "failed to initialize magic cookie"); 55 | } 56 | 57 | if(magic_load(cookie, path)) { 58 | rb_raise(eMahoroError, "failed to load database: %s", 59 | magic_error(cookie)); 60 | } 61 | 62 | ptr = ALLOC(struct MagicCookie); 63 | ptr->cookie = cookie; 64 | DATA_PTR(self) = ptr; 65 | 66 | return self; 67 | } 68 | 69 | static VALUE 70 | mahoro_file(self, path) 71 | VALUE self, path; 72 | { 73 | const char *msg; 74 | magic_t cookie = ((struct MagicCookie *) DATA_PTR(self))->cookie; 75 | 76 | if(!(msg = magic_file(cookie, StringValuePtr(path)))) { 77 | rb_raise(eMahoroError, "failed lookup: %s", magic_error(cookie)); 78 | } 79 | 80 | return rb_str_new2(msg); 81 | } 82 | 83 | static VALUE 84 | mahoro_buffer(self, input) 85 | VALUE self, input; 86 | { 87 | const char *msg; 88 | magic_t cookie = ((struct MagicCookie *) DATA_PTR(self))->cookie; 89 | 90 | if(!(msg = magic_buffer(cookie, StringValuePtr(input), 91 | RSTRING(StringValue(input))->len))) { 92 | rb_raise(eMahoroError, "failed lookup: %s", magic_error(cookie)); 93 | } 94 | 95 | return rb_str_new2(msg); 96 | } 97 | 98 | static VALUE 99 | mahoro_set_flags(self, flags) 100 | VALUE self, flags; 101 | { 102 | magic_t cookie = ((struct MagicCookie *) DATA_PTR(self))->cookie; 103 | 104 | return INT2FIX(magic_setflags(cookie, FIX2INT(flags))); 105 | } 106 | 107 | static VALUE 108 | mahoro_check(argc, argv, self) 109 | int argc; 110 | VALUE *argv, self; 111 | { 112 | char *path = 0; 113 | VALUE vpath; 114 | magic_t cookie = ((struct MagicCookie *) DATA_PTR(self))->cookie; 115 | 116 | switch(rb_scan_args(argc, argv, "01", &vpath)) { 117 | case 1: 118 | if(!NIL_P(vpath)) { 119 | path = StringValuePtr(vpath); 120 | } 121 | break; 122 | } 123 | 124 | if(!magic_check(cookie, path)) { 125 | return Qtrue; 126 | } else { 127 | return Qfalse; 128 | } 129 | } 130 | 131 | static VALUE 132 | mahoro_compile(klass, path) 133 | VALUE klass, path; 134 | { 135 | magic_t cookie = magic_open(MAGIC_NONE); 136 | 137 | if(magic_compile(cookie, StringValuePtr(path))) { 138 | rb_raise(eMahoroError, "failed compile: %s", magic_error(cookie)); 139 | } 140 | 141 | magic_close(cookie); 142 | 143 | return Qtrue; 144 | } 145 | 146 | static VALUE 147 | mahoro_load(self, path) 148 | VALUE self, path; 149 | { 150 | magic_t cookie = ((struct MagicCookie *) DATA_PTR(self))->cookie; 151 | 152 | if(magic_load(cookie, StringValuePtr(path))) { 153 | rb_raise(eMahoroError, "failed load: %s", magic_error(cookie)); 154 | } 155 | 156 | return self; 157 | } 158 | 159 | void Init_mahoro(void) 160 | { 161 | cMahoro = rb_define_class("Mahoro", rb_cObject); 162 | eMahoroError = rb_define_class_under(cMahoro, "Error", rb_eStandardError); 163 | 164 | rb_const_set(cMahoro, rb_intern("NONE"), INT2FIX(MAGIC_NONE)); 165 | rb_const_set(cMahoro, rb_intern("DEBUG"), INT2FIX(MAGIC_DEBUG)); 166 | rb_const_set(cMahoro, rb_intern("SYMLINK"), INT2FIX(MAGIC_SYMLINK)); 167 | rb_const_set(cMahoro, rb_intern("COMPRESS"), INT2FIX(MAGIC_COMPRESS)); 168 | rb_const_set(cMahoro, rb_intern("DEVICES"), INT2FIX(MAGIC_DEVICES)); 169 | rb_const_set(cMahoro, rb_intern("MIME"), INT2FIX(MAGIC_MIME)); 170 | rb_const_set(cMahoro, rb_intern("CONTINUE"), INT2FIX(MAGIC_CONTINUE)); 171 | rb_const_set(cMahoro, rb_intern("CHECK"), INT2FIX(MAGIC_CHECK)); 172 | rb_const_set(cMahoro, rb_intern("PRESERVE_ATIME"), 173 | INT2FIX(MAGIC_PRESERVE_ATIME)); 174 | rb_const_set(cMahoro, rb_intern("RAW"), INT2FIX(MAGIC_RAW)); 175 | rb_const_set(cMahoro, rb_intern("ERROR"), INT2FIX(MAGIC_ERROR)); 176 | 177 | rb_define_alloc_func(cMahoro, mahoro_allocate); 178 | rb_define_method(cMahoro, "initialize", mahoro_initialize, -1); 179 | rb_define_method(cMahoro, "file", mahoro_file, 1); 180 | rb_define_method(cMahoro, "buffer", mahoro_buffer, 1); 181 | rb_define_method(cMahoro, "flags=", mahoro_set_flags, 1); 182 | rb_define_method(cMahoro, "valid?", mahoro_check, -1); 183 | rb_define_singleton_method(cMahoro, "compile", mahoro_compile, 1); 184 | rb_define_method(cMahoro, "load", mahoro_load, 1); 185 | } 186 | 187 | /* arch-tag: mahoro */ 188 | -------------------------------------------------------------------------------- /lib/filemagic.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require "filemagic/#{RUBY_VERSION[/\d+.\d+/]}/ruby_filemagic" 3 | rescue LoadError => err 4 | raise if err.respond_to?(:path) && !err.path 5 | require 'filemagic/ruby_filemagic' 6 | end 7 | 8 | require 'filemagic/version' 9 | 10 | class FileMagic 11 | 12 | unless ENV['MAGIC_SILENCE_VERSION_CHECK'] || MAGIC_VERSION == library_version 13 | warn "#{self} v#{VERSION}: compiled magic version [#{MAGIC_VERSION}] " << 14 | "does not match with shared library magic version [#{library_version}]" 15 | end 16 | 17 | DEFAULT_MAGIC = __FILE__.sub(/\.rb\z/, '/magic.mgc') 18 | 19 | ENV['MAGIC'] ||= DEFAULT_MAGIC unless path 20 | 21 | # Map flag names to their values (:name => Integer). 22 | FLAGS_BY_SYM = [ 23 | :none, # No flags 24 | :debug, # Turn on debugging 25 | :symlink, # Follow symlinks 26 | :compress, # Check inside compressed files 27 | :devices, # Look at the contents of devices 28 | :mime_type, # Return the MIME type 29 | :continue, # Return all matches 30 | :check, # Print warnings to stderr 31 | :preserve_atime, # Restore access time on exit 32 | :raw, # Don't convert unprintable chars 33 | :error, # Handle ENOENT etc as real errors 34 | :mime_encoding, # Return the MIME encoding 35 | :mime, # MAGIC_MIME_TYPE | MAGIC_MIME_ENCODING 36 | :apple, # Return the Apple creator/type 37 | :extension, # Return a /-separated list of extensions 38 | :compress_transp, # Check inside compressed files but not report compression 39 | :nodesc, # MAGIC_EXTENSION | MAGIC_MIME | MAGIC_APPLE 40 | :no_check_compress, # Don't check for compressed files 41 | :no_check_tar, # Don't check for tar files 42 | :no_check_soft, # Don't check magic entries 43 | :no_check_apptype, # Don't check application type 44 | :no_check_elf, # Don't check for elf details 45 | :no_check_text, # Don't check for text files 46 | :no_check_cdf, # Don't check for cdf files 47 | :no_check_tokens, # Don't check tokens 48 | :no_check_encoding, # Don't check text encodings 49 | :no_check_builtin, # No built-in tests; only consult the magic file 50 | 51 | # Defined for backwards compatibility (renamed) 52 | :no_check_ascii, # MAGIC_NO_CHECK_TEXT 53 | 54 | # Defined for backwards compatibility; do nothing 55 | :no_check_fortran, # Don't check ascii/fortran 56 | :no_check_troff # Don't check ascii/troff 57 | ].inject({}) { |flags, flag| 58 | const = "MAGIC_#{flag.to_s.upcase}" 59 | flags.update(flag => const_defined?(const) && const_get(const)) 60 | } 61 | 62 | # Map flag values to their names (Integer => :name). 63 | FLAGS_BY_INT = FLAGS_BY_SYM.invert.update(0 => :none) 64 | 65 | # Extract "simple" MIME type. 66 | SIMPLE_RE = %r{([.\w\/-]+)} 67 | 68 | @fm = {} 69 | 70 | class << self 71 | 72 | # Provide a "magic singleton". 73 | def fm(*flags) 74 | options = flags.last.is_a?(Hash) ? flags.pop : {} 75 | 76 | if fm = @fm[key = [flags = flags(flags), options]] 77 | return fm unless fm.closed? 78 | end 79 | 80 | @fm[key] = new(flags, options) 81 | end 82 | 83 | # Clear our instance cache. 84 | def clear! 85 | @fm.each_value(&:close).clear 86 | end 87 | 88 | # Just like #new, but takes an optional block, in which case #close 89 | # is called at the end and the value of the block is returned. 90 | def open(*flags) 91 | fm = new(*flags) 92 | 93 | if block_given? 94 | begin 95 | yield fm 96 | ensure 97 | fm.close 98 | end 99 | else 100 | fm 101 | end 102 | end 103 | 104 | # Just a short-cut to #open with the +mime+ flag set. 105 | def mime(*flags, &block) 106 | open(:mime, *flags, &block) 107 | end 108 | 109 | def magic_version(default = MAGIC_VERSION) 110 | default != '0' ? default : 111 | user_magic_version || 112 | auto_magic_version || 113 | [default, 'unknown'] 114 | end 115 | 116 | private 117 | 118 | def user_magic_version(key = 'MAGIC_VERSION') 119 | [ENV[key], 'user-specified'] if ENV[key] 120 | end 121 | 122 | def auto_magic_version 123 | require 'nuggets/file/which' 124 | 125 | if cmd = File.which_command([ 126 | 'dpkg-query -f \'${Version}\' -W libmagic-dev', 127 | 'file -v' 128 | ]) 129 | [%x{#{cmd}}[/\d+\.\d+/], 'auto-detected'] 130 | end 131 | rescue LoadError 132 | end 133 | 134 | end 135 | 136 | attr_writer :simplified 137 | 138 | def simplified? 139 | @simplified 140 | end 141 | 142 | def io(io, length = 8, rewind = false) 143 | pos = io.pos if rewind 144 | buffer(io.read(length)) 145 | ensure 146 | io.pos = pos if pos 147 | end 148 | 149 | def fd(fd) 150 | descriptor(fd.respond_to?(:fileno) ? fd.fileno : fd) 151 | end 152 | 153 | def inspect 154 | super.insert(-2, closed? ? ' (closed)' : '') 155 | end 156 | 157 | end 158 | -------------------------------------------------------------------------------- /test/filemagic_test.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'filemagic' 3 | 4 | class TestFileMagic < Test::Unit::TestCase 5 | 6 | magic_version, origin = FileMagic.magic_version 7 | MAGIC_VERSION = magic_version.to_f 8 | 9 | warn <<-EOT 10 | 11 | libmagic version: #{MAGIC_VERSION}#{" (#{origin})" if origin} 12 | magic file from #{FileMagic.path} 13 | 14 | EOT 15 | 16 | def test_file 17 | fm = FileMagic.new(FileMagic::MAGIC_NONE) 18 | 19 | python_script = match_version( 20 | 0 => 'a python script, ASCII text executable', 21 | 5.11 => 'Python script, ASCII text executable' 22 | ) 23 | 24 | res = fm.file(path_to('pyfile')) 25 | assert_equal(python_script, res) 26 | 27 | if File.symlink?(path_to('pylink')) 28 | res = fm.file(path_to('pylink')) 29 | assert_equal(match_version( 30 | 0 => "symbolic link to `pyfile'", 31 | 5.22 => 'symbolic link to pyfile' 32 | ), res.strip) 33 | end 34 | 35 | fm.close 36 | fm = FileMagic.new(FileMagic::MAGIC_SYMLINK) 37 | 38 | res = fm.file(path_to('pylink')) 39 | assert_equal(python_script, res) 40 | 41 | fm.close 42 | fm = FileMagic.new(FileMagic::MAGIC_SYMLINK | FileMagic::MAGIC_MIME) 43 | 44 | res = fm.file(path_to('pylink')) 45 | assert_equal('text/plain; charset=us-ascii', res) 46 | 47 | fm.close 48 | fm = FileMagic.new(FileMagic::MAGIC_COMPRESS) 49 | 50 | res = fm.file(path_to('pyfile-compressed.gz')) 51 | gzip_compressed = 'gzip compressed data, was "pyfile-compressed"' 52 | assert_match(Gem.win_platform? ? /^#{gzip_compressed}/ : 53 | /^#{python_script} \(#{gzip_compressed}/, res) 54 | 55 | fm.close 56 | end 57 | 58 | def test_buffer 59 | fm = FileMagic.new(FileMagic::MAGIC_NONE) 60 | res = fm.buffer("#!/bin/sh\n") 61 | fm.close 62 | assert_equal('POSIX shell script, ASCII text executable', res) 63 | end 64 | 65 | def test_nil_buffer 66 | fm = FileMagic.new(FileMagic::MAGIC_NONE) 67 | assert_raise(TypeError) { fm.buffer(nil) } 68 | fm.close 69 | end 70 | 71 | def test_descriptor 72 | fm = FileMagic.new(FileMagic::MAGIC_NONE) 73 | 74 | python_script = match_version( 75 | 0 => 'a python script, ASCII text executable', 76 | 5.11 => 'Python script, ASCII text executable' 77 | ) 78 | 79 | fd_for('pyfile') { |fd| 80 | res = fm.descriptor(fd) 81 | assert_equal(python_script, res) 82 | } 83 | 84 | if File.symlink?(path_to('pylink')) 85 | fd_for('pylink') { |fd| 86 | res = fm.descriptor(fd) 87 | assert_equal(python_script, res.strip) 88 | } 89 | end 90 | 91 | fm.close 92 | fm = FileMagic.new(FileMagic::MAGIC_SYMLINK) 93 | 94 | fd_for('pylink') { |fd| 95 | res = fm.descriptor(fd) 96 | assert_equal(python_script, res) 97 | } 98 | 99 | fm.close 100 | fm = FileMagic.new(FileMagic::MAGIC_SYMLINK | FileMagic::MAGIC_MIME) 101 | 102 | fd_for('pylink') { |fd| 103 | res = fm.descriptor(fd) 104 | assert_equal('text/plain; charset=us-ascii', res) 105 | } 106 | 107 | fm.close 108 | fm = FileMagic.new(FileMagic::MAGIC_COMPRESS) 109 | 110 | fd_for('pyfile-compressed.gz') { |fd| 111 | res = fm.descriptor(fd) 112 | gzip_compressed = 'gzip compressed data, was "pyfile-compressed"' 113 | assert_match(Gem.win_platform? ? /^#{gzip_compressed}/ : 114 | /^#{python_script} \(#{gzip_compressed}/, res) 115 | } 116 | 117 | fm.close 118 | end 119 | 120 | def test_check 121 | return if Gem.win_platform? 122 | fm = FileMagic.new(FileMagic::MAGIC_NONE) 123 | res = silence_stderr { fm.check(path_to('perl')) } 124 | fm.close 125 | assert(res) 126 | end 127 | 128 | def test_check_compiled 129 | return if MAGIC_VERSION <= 5.09 130 | fm = FileMagic.new(FileMagic::MAGIC_NONE) 131 | res = silence_stderr { fm.check(path_to('perl.mgc')) } 132 | fm.close 133 | assert(match_version( 134 | 0 => res, 135 | 5.39 => !res 136 | )) 137 | end 138 | 139 | def test_compile 140 | assert(File.writable?('.'), "can't write to current directory") 141 | fm = FileMagic.new(FileMagic::MAGIC_NONE) 142 | res = fm.compile(path_to('perl')) 143 | fm.close 144 | assert(res) 145 | File.unlink(path_to('perl.mgc', '.')) 146 | end 147 | 148 | def test_block 149 | block_fm = nil 150 | res = FileMagic.open(FileMagic::MAGIC_NONE) { |fm| 151 | block_fm = fm 152 | fm.file(path_to('pyfile')) 153 | } 154 | assert_equal(match_version( 155 | 0 => 'a python script, ASCII text executable', 156 | 5.11 => 'Python script, ASCII text executable' 157 | ), res) 158 | assert block_fm.closed? 159 | end 160 | 161 | def test_flags_to_int 162 | assert_raise(TypeError) { FileMagic.flags(0) } 163 | assert_equal(0, FileMagic.flags([FileMagic::MAGIC_NONE])) 164 | assert_equal(0, FileMagic.flags([:none])) 165 | assert_equal(0, FileMagic.flags([])) 166 | assert_equal(1072, FileMagic.flags([:mime, :continue])) 167 | end 168 | 169 | def test_setflags 170 | fm = FileMagic.new(FileMagic::MAGIC_NONE) 171 | assert_equal([], fm.flags) 172 | fm.flags = FileMagic::MAGIC_SYMLINK 173 | assert_equal([:symlink], fm.flags) 174 | fm.close 175 | end 176 | 177 | def test_abbr 178 | fm = FileMagic.new(:mime, :continue) 179 | assert_equal([:mime_type, :continue, :mime_encoding], fm.flags) 180 | fm.flags = :symlink 181 | assert_equal([:symlink], fm.flags) 182 | fm.close 183 | end 184 | 185 | def test_close 186 | fm = FileMagic.new 187 | fm.close 188 | assert fm.closed? 189 | fm.close 190 | assert fm.closed? 191 | end 192 | 193 | # tests adapted from mahoro: 194 | 195 | def test_mahoro_file 196 | fm = FileMagic.new 197 | fm.flags = FileMagic::MAGIC_NONE 198 | assert_equal(match_version( 199 | 0 => 'ASCII C program text', 200 | 5.11 => 'C source, ASCII text' 201 | ), fm.file(path_to('mahoro.c'))) 202 | end 203 | 204 | def test_mahoro_mime_file 205 | fm = FileMagic.new 206 | fm.flags = FileMagic::MAGIC_MIME 207 | assert_equal('text/x-c; charset=us-ascii', fm.file(path_to('mahoro.c'))) 208 | end 209 | 210 | def test_mahoro_buffer 211 | fm = FileMagic.new 212 | fm.flags = FileMagic::MAGIC_NONE 213 | assert_equal(match_version( 214 | 0 => 'ASCII C program text', 215 | 5.11 => 'C source, ASCII text' 216 | ), fm.buffer(File.read(path_to('mahoro.c')))) 217 | end 218 | 219 | def test_mahoro_mime_buffer 220 | fm = FileMagic.new 221 | fm.flags = FileMagic::MAGIC_MIME 222 | assert_equal('text/x-c; charset=us-ascii', fm.buffer(File.read(path_to('mahoro.c')))) 223 | end 224 | 225 | def test_mahoro_valid 226 | fm = FileMagic.new 227 | assert(silence_stderr { fm.valid? }, 'Default database was not valid.') 228 | end 229 | 230 | # test abbreviating mime types 231 | 232 | def test_abbrev_mime_type 233 | fm = FileMagic.mime 234 | 235 | refute fm.simplified? 236 | assert_equal('text/plain; charset=us-ascii', fm.file(path_to('perl'))) 237 | 238 | fm.simplified = true 239 | assert fm.simplified? 240 | assert_equal('text/plain', fm.file(path_to('perl'))) 241 | assert_equal(match_version( 242 | 0 => 'application/vnd.ms-office', 243 | 5.11 => 'application/msword', 244 | 5.14 => 'application/vnd.ms-office', 245 | 5.39 => 'application/vnd.ms-excel' 246 | ), fm.file(path_to('excel-example.xls'))) 247 | end 248 | 249 | def test_singleton 250 | fm1 = FileMagic.fm 251 | assert_equal(fm1, FileMagic.fm) 252 | 253 | refute fm1.simplified? 254 | assert_equal('ASCII text', fm1.file(path_to('perl'))) 255 | 256 | fm2 = FileMagic.fm(:mime) 257 | assert_equal(fm2, FileMagic.fm(:mime)) 258 | refute_equal(fm2, fm1) 259 | 260 | refute fm2.simplified? 261 | assert_equal('text/plain; charset=us-ascii', fm2.file(path_to('perl'))) 262 | 263 | fm3 = FileMagic.fm(:mime, simplified: true) 264 | assert_equal(fm3, FileMagic.fm(:mime, simplified: true)) 265 | refute_equal(fm3, fm2) 266 | refute_equal(fm3, fm1) 267 | 268 | assert fm3.simplified? 269 | assert_equal('text/plain', fm3.file(path_to('perl'))) 270 | end 271 | 272 | # utility methods: 273 | 274 | def path_to(file, dir = File.dirname(__FILE__)) 275 | File.join(dir, file) 276 | end 277 | 278 | def fd_for(file) 279 | File.open(path_to(file)) { |f| yield f.fileno } 280 | rescue Errno::EBADF => err 281 | warn err.to_s 282 | end 283 | 284 | def silence_stderr 285 | require 'nuggets/io/redirect' 286 | $stderr.redirect { yield } 287 | rescue LoadError 288 | yield 289 | end 290 | 291 | def match_version(versions) 292 | versions.sort_by { |k,| -k }.find { |k,| k <= MAGIC_VERSION }.last 293 | end 294 | 295 | end 296 | -------------------------------------------------------------------------------- /ext/filemagic/filemagic.c: -------------------------------------------------------------------------------- 1 | #include "filemagic.h" 2 | 3 | /* Returns the magic version */ 4 | static VALUE 5 | rb_magic_version(VALUE klass) { 6 | char version[8] = "0"; 7 | #ifdef HAVE_MAGIC_VERSION 8 | RB_MAGIC_SET_VERSION(magic_version() / 100, magic_version() % 100) 9 | #endif 10 | return rb_str_new2(version); 11 | } 12 | 13 | /* Returns the magic path */ 14 | static VALUE 15 | rb_magic_getpath(VALUE klass) { 16 | const char *path = magic_getpath(NULL, 0); 17 | return path != NULL ? rb_str_new2(path) : Qnil; 18 | } 19 | 20 | /* Converts flags to integer */ 21 | static VALUE 22 | rb_magic_flags(VALUE klass, VALUE flags) { 23 | VALUE map = rb_const_get(cFileMagic, rb_intern("FLAGS_BY_SYM")), f, g; 24 | int i = MAGIC_NONE, j; 25 | 26 | if (TYPE(flags) != T_ARRAY) { 27 | rb_raise(rb_eTypeError, 28 | "wrong argument type %s (expected Array)", 29 | rb_obj_classname(flags)); 30 | } 31 | 32 | for (j = 0; j < RARRAY_LEN(flags); j++) { 33 | f = rb_ary_entry(flags, j); 34 | 35 | switch (TYPE(f)) { 36 | case T_SYMBOL: 37 | if (RTEST(g = rb_hash_aref(map, f))) { 38 | f = g; 39 | /* fall through */ 40 | } 41 | else { 42 | f = rb_funcall(f, rb_intern("inspect"), 0); 43 | rb_raise(rb_eArgError, "%s: %s", 44 | NIL_P(g) ? "no such flag" : "flag not available", 45 | StringValueCStr(f)); 46 | 47 | break; 48 | } 49 | case T_FIXNUM: 50 | i |= NUM2INT(f); 51 | break; 52 | default: 53 | rb_raise(rb_eTypeError, 54 | "wrong argument type %s (expected Fixnum or Symbol)", 55 | rb_obj_classname(f)); 56 | } 57 | } 58 | 59 | return INT2FIX(i); 60 | } 61 | 62 | /* FileMagic.new */ 63 | static VALUE 64 | rb_magic_new(int argc, VALUE *argv, VALUE klass) { 65 | VALUE obj, args[2]; 66 | magic_t ms; 67 | 68 | if (rb_block_given_p()) { 69 | rb_warn( 70 | "FileMagic.new() does not take a block; use FileMagic.open() instead"); 71 | } 72 | 73 | if (argc > 0 && TYPE(args[1] = argv[argc - 1]) == T_HASH) { 74 | argc--; 75 | } 76 | else { 77 | args[1] = rb_hash_new(); 78 | } 79 | 80 | args[0] = rb_magic_flags(klass, rb_ary_new4(argc, argv)); 81 | 82 | if ((ms = magic_open(NUM2INT(args[0]))) == NULL) { 83 | rb_raise(rb_FileMagicError, 84 | "failed to initialize magic cookie (%d)", errno || -1); 85 | } 86 | 87 | if (magic_load(ms, NULL) == -1) { 88 | rb_raise(rb_FileMagicError, 89 | "failed to load database: %s", magic_error(ms)); 90 | } 91 | 92 | obj = Data_Wrap_Struct(klass, 0, rb_magic_free, ms); 93 | rb_obj_call_init(obj, 2, args); 94 | 95 | return obj; 96 | } 97 | 98 | static void 99 | rb_magic_free(magic_t ms) { 100 | magic_close(ms); 101 | } 102 | 103 | static VALUE 104 | rb_magic_init(int argc, VALUE *argv, VALUE self) { 105 | VALUE flags, options, keys, k, m; 106 | ID mid; 107 | int i; 108 | 109 | if (rb_scan_args(argc, argv, "11", &flags, &options) == 1) { 110 | options = rb_hash_new(); 111 | } 112 | 113 | rb_iv_set(self, "iflags", flags); 114 | rb_iv_set(self, "closed", Qfalse); 115 | rb_iv_set(self, "@simplified", Qfalse); 116 | 117 | keys = rb_funcall(options, rb_intern("keys"), 0); 118 | 119 | for (i = 0; i < RARRAY_LEN(keys); i++) { 120 | k = rb_ary_entry(keys, i); 121 | m = rb_str_plus(rb_obj_as_string(k), rb_str_new2("=")); 122 | 123 | if (rb_respond_to(self, mid = rb_intern(StringValueCStr(m)))) { 124 | rb_funcall(self, mid, 1, rb_hash_aref(options, k)); 125 | } 126 | else { 127 | k = rb_funcall(k, rb_intern("inspect"), 0); 128 | rb_raise(rb_eArgError, "illegal option: %s", StringValueCStr(k)); 129 | } 130 | } 131 | 132 | return Qnil; 133 | } 134 | 135 | /* Frees resources allocated */ 136 | static VALUE 137 | rb_magic_close(VALUE self) { 138 | magic_t ms; 139 | 140 | if (RTEST(rb_magic_closed_p(self))) { 141 | return Qnil; 142 | } 143 | 144 | GetMagicSet(self, ms); 145 | rb_magic_free(ms); 146 | 147 | /* This keeps rb_magic_free from trying to free closed objects */ 148 | DATA_PTR(self) = NULL; 149 | 150 | rb_iv_set(self, "closed", Qtrue); 151 | 152 | return Qnil; 153 | } 154 | 155 | static VALUE 156 | rb_magic_closed_p(VALUE self) { 157 | return rb_attr_get(self, rb_intern("closed")); 158 | } 159 | 160 | /* Return a string describing the named file */ 161 | RB_MAGIC_TYPE(file, FILE) 162 | 163 | /* Return a string describing the string buffer */ 164 | RB_MAGIC_TYPE(buffer, BUFFER) 165 | 166 | /* Return a string describing the file descriptor */ 167 | RB_MAGIC_TYPE(descriptor, DESCRIPTOR) 168 | 169 | /* Get the flags as array of symbols */ 170 | static VALUE 171 | rb_magic_getflags(VALUE self) { 172 | VALUE ary = rb_ary_new(); 173 | VALUE map = rb_const_get(cFileMagic, rb_intern("FLAGS_BY_INT")); 174 | int i = NUM2INT(rb_attr_get(self, rb_intern("iflags"))), j = 0; 175 | 176 | while ((i -= j) > 0) { 177 | j = pow(2, (int)(log(i) / log(2))); 178 | rb_ary_unshift(ary, rb_hash_aref(map, INT2FIX(j))); 179 | } 180 | 181 | return ary; 182 | } 183 | 184 | /* Set flags on the ms object */ 185 | static VALUE 186 | rb_magic_setflags(VALUE self, VALUE flags) { 187 | magic_t ms; 188 | 189 | GetMagicSet(self, ms); 190 | 191 | rb_iv_set(self, "iflags", 192 | flags = rb_magic_flags(CLASS_OF(self), rb_Array(flags))); 193 | 194 | return INT2FIX(magic_setflags(ms, NUM2INT(flags))); 195 | } 196 | 197 | /* Lists a magic database file */ 198 | RB_MAGIC_APPRENTICE(list) 199 | 200 | /* Loads a magic database file */ 201 | RB_MAGIC_APPRENTICE(load) 202 | 203 | /* Checks validity of a magic database file */ 204 | RB_MAGIC_APPRENTICE(check) 205 | 206 | /* Compiles a magic database file */ 207 | RB_MAGIC_APPRENTICE(compile) 208 | 209 | void 210 | Init_ruby_filemagic() { 211 | char version[8] = "0"; 212 | cFileMagic = rb_define_class("FileMagic", rb_cObject); 213 | 214 | #if defined(FILE_VERSION_MAJOR) 215 | RB_MAGIC_SET_VERSION(FILE_VERSION_MAJOR, patchlevel) 216 | #elif defined(MAGIC_VERSION) 217 | RB_MAGIC_SET_VERSION(MAGIC_VERSION / 100, MAGIC_VERSION % 100) 218 | #endif 219 | 220 | rb_define_const(cFileMagic, "MAGIC_VERSION", rb_str_new2(version)); 221 | 222 | rb_define_singleton_method(cFileMagic, "library_version", rb_magic_version, 0); 223 | rb_define_singleton_method(cFileMagic, "path", rb_magic_getpath, 0); 224 | rb_define_singleton_method(cFileMagic, "flags", rb_magic_flags, 1); 225 | rb_define_singleton_method(cFileMagic, "new", rb_magic_new, -1); 226 | 227 | rb_define_method(cFileMagic, "initialize", rb_magic_init, -1); 228 | rb_define_method(cFileMagic, "close", rb_magic_close, 0); 229 | rb_define_method(cFileMagic, "closed?", rb_magic_closed_p, 0); 230 | rb_define_method(cFileMagic, "file", rb_magic_file, -1); 231 | rb_define_method(cFileMagic, "buffer", rb_magic_buffer, -1); 232 | rb_define_method(cFileMagic, "descriptor", rb_magic_descriptor, -1); 233 | rb_define_method(cFileMagic, "flags", rb_magic_getflags, 0); 234 | rb_define_method(cFileMagic, "flags=", rb_magic_setflags, 1); 235 | rb_define_method(cFileMagic, "list", rb_magic_list, -1); 236 | rb_define_method(cFileMagic, "load", rb_magic_load, -1); 237 | rb_define_method(cFileMagic, "check", rb_magic_check, -1); 238 | rb_define_method(cFileMagic, "compile", rb_magic_compile, -1); 239 | 240 | rb_alias(cFileMagic, rb_intern("valid?"), rb_intern("check")); 241 | 242 | rb_FileMagicError = rb_define_class_under(cFileMagic, "FileMagicError", rb_eStandardError); 243 | 244 | #ifdef MAGIC_NONE 245 | rb_define_const(cFileMagic, "MAGIC_NONE", INT2FIX(MAGIC_NONE)); 246 | #endif 247 | #ifdef MAGIC_DEBUG 248 | rb_define_const(cFileMagic, "MAGIC_DEBUG", INT2FIX(MAGIC_DEBUG)); 249 | #endif 250 | #ifdef MAGIC_SYMLINK 251 | rb_define_const(cFileMagic, "MAGIC_SYMLINK", INT2FIX(MAGIC_SYMLINK)); 252 | #endif 253 | #ifdef MAGIC_COMPRESS 254 | rb_define_const(cFileMagic, "MAGIC_COMPRESS", INT2FIX(MAGIC_COMPRESS)); 255 | #endif 256 | #ifdef MAGIC_DEVICES 257 | rb_define_const(cFileMagic, "MAGIC_DEVICES", INT2FIX(MAGIC_DEVICES)); 258 | #endif 259 | #ifdef MAGIC_MIME_TYPE 260 | rb_define_const(cFileMagic, "MAGIC_MIME_TYPE", INT2FIX(MAGIC_MIME_TYPE)); 261 | #endif 262 | #ifdef MAGIC_CONTINUE 263 | rb_define_const(cFileMagic, "MAGIC_CONTINUE", INT2FIX(MAGIC_CONTINUE)); 264 | #endif 265 | #ifdef MAGIC_CHECK 266 | rb_define_const(cFileMagic, "MAGIC_CHECK", INT2FIX(MAGIC_CHECK)); 267 | #endif 268 | #ifdef MAGIC_PRESERVE_ATIME 269 | rb_define_const(cFileMagic, "MAGIC_PRESERVE_ATIME", INT2FIX(MAGIC_PRESERVE_ATIME)); 270 | #endif 271 | #ifdef MAGIC_RAW 272 | rb_define_const(cFileMagic, "MAGIC_RAW", INT2FIX(MAGIC_RAW)); 273 | #endif 274 | #ifdef MAGIC_ERROR 275 | rb_define_const(cFileMagic, "MAGIC_ERROR", INT2FIX(MAGIC_ERROR)); 276 | #endif 277 | #ifdef MAGIC_MIME_ENCODING 278 | rb_define_const(cFileMagic, "MAGIC_MIME_ENCODING", INT2FIX(MAGIC_MIME_ENCODING)); 279 | #endif 280 | #ifdef MAGIC_MIME 281 | rb_define_const(cFileMagic, "MAGIC_MIME", INT2FIX(MAGIC_MIME)); 282 | #endif 283 | #ifdef MAGIC_APPLE 284 | rb_define_const(cFileMagic, "MAGIC_APPLE", INT2FIX(MAGIC_APPLE)); 285 | #endif 286 | #ifdef MAGIC_EXTENSION 287 | rb_define_const(cFileMagic, "MAGIC_EXTENSION", INT2FIX(MAGIC_EXTENSION)); 288 | #endif 289 | #ifdef MAGIC_COMPRESS_TRANSP 290 | rb_define_const(cFileMagic, "MAGIC_COMPRESS_TRANSP", INT2FIX(MAGIC_COMPRESS_TRANSP)); 291 | #endif 292 | #ifdef MAGIC_NODESC 293 | rb_define_const(cFileMagic, "MAGIC_NODESC", INT2FIX(MAGIC_NODESC)); 294 | #endif 295 | #ifdef MAGIC_NO_CHECK_COMPRESS 296 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_COMPRESS", INT2FIX(MAGIC_NO_CHECK_COMPRESS)); 297 | #endif 298 | #ifdef MAGIC_NO_CHECK_TAR 299 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_TAR", INT2FIX(MAGIC_NO_CHECK_TAR)); 300 | #endif 301 | #ifdef MAGIC_NO_CHECK_SOFT 302 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_SOFT", INT2FIX(MAGIC_NO_CHECK_SOFT)); 303 | #endif 304 | #ifdef MAGIC_NO_CHECK_APPTYPE 305 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_APPTYPE", INT2FIX(MAGIC_NO_CHECK_APPTYPE)); 306 | #endif 307 | #ifdef MAGIC_NO_CHECK_ELF 308 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_ELF", INT2FIX(MAGIC_NO_CHECK_ELF)); 309 | #endif 310 | #ifdef MAGIC_NO_CHECK_TEXT 311 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_TEXT", INT2FIX(MAGIC_NO_CHECK_TEXT)); 312 | #endif 313 | #ifdef MAGIC_NO_CHECK_CDF 314 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_CDF", INT2FIX(MAGIC_NO_CHECK_CDF)); 315 | #endif 316 | #ifdef MAGIC_NO_CHECK_TOKENS 317 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_TOKENS", INT2FIX(MAGIC_NO_CHECK_TOKENS)); 318 | #endif 319 | #ifdef MAGIC_NO_CHECK_ENCODING 320 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_ENCODING", INT2FIX(MAGIC_NO_CHECK_ENCODING)); 321 | #endif 322 | #if defined(MAGIC_NO_CHECK_BUILTIN) && MAGIC_VERSION > 514 323 | /* defined in b5be901 (2010-01-28, 5.05), but broken until 38e0136 (2013-08-15, 5.15) */ 324 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_BUILTIN", INT2FIX(MAGIC_NO_CHECK_BUILTIN)); 325 | #endif 326 | #ifdef MAGIC_NO_CHECK_ASCII 327 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_ASCII", INT2FIX(MAGIC_NO_CHECK_ASCII)); 328 | #endif 329 | #ifdef MAGIC_NO_CHECK_FORTRAN 330 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_FORTRAN", INT2FIX(MAGIC_NO_CHECK_FORTRAN)); 331 | #endif 332 | #ifdef MAGIC_NO_CHECK_TROFF 333 | rb_define_const(cFileMagic, "MAGIC_NO_CHECK_TROFF", INT2FIX(MAGIC_NO_CHECK_TROFF)); 334 | #endif 335 | } 336 | --------------------------------------------------------------------------------