magikku 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,278 @@
1
+ require 'ffi'
2
+ require 'ffi/libmagic'
3
+
4
+ require 'magikku/convenience'
5
+
6
+ # Note the implementation of this class may either be via FFI
7
+ # or via native C bindings depending on your installation and
8
+ # what version of ruby you are using.
9
+ class MagikkuFFI
10
+ extend MagikkuHelpers
11
+
12
+ # Defines various flags that can be passed when creating a magic scan object
13
+ # using Magikku.new or afterwards using Magikku.flags=
14
+ module Flags
15
+ # No flags
16
+ NONE = 0x000000
17
+
18
+ # Turn on debugging
19
+ DEBUG = 0x000001
20
+
21
+ # Follow symlinks
22
+ SYMLINK = 0x000002
23
+
24
+ # Check inside compressed files
25
+ COMPRESS = 0x000004
26
+
27
+ # Look at the contents of devices
28
+ DEVICES = 0x000008
29
+
30
+ # Return the MIME type
31
+ MIME_TYPE = 0x000010
32
+
33
+ # Return all matches
34
+ CONTINUE = 0x000020
35
+
36
+ # Print warnings to stderr
37
+ CHECK = 0x000040
38
+
39
+ # Restore access time on exit
40
+ PRESERVE_ATIME = 0x000080
41
+
42
+ # Don't translate unprintable chars
43
+ RAW = 0x000100
44
+
45
+ # Handle ENOENT etc as real errors
46
+ ERROR = 0x000200
47
+
48
+ # Return the MIME encoding
49
+ MIME_ENCODING = 0x000400
50
+
51
+ # alias for (MAGIC_MIME_TYPE|MAGIC_MIME_ENCODING)
52
+ MIME = (MIME_TYPE|MIME_ENCODING)
53
+
54
+ # Return the Apple creator and type
55
+ APPLE = 0x000800
56
+
57
+ # Don't check for compressed files
58
+ NO_CHECK_COMPRESS = 0x001000
59
+
60
+ # Don't check for tar files
61
+ NO_CHECK_TAR = 0x002000
62
+
63
+ # Don't check magic entries
64
+ NO_CHECK_SOFT = 0x004000
65
+
66
+ # Don't check application type
67
+ NO_CHECK_APPTYPE = 0x008000
68
+
69
+ # Don't check for elf details
70
+ NO_CHECK_ELF = 0x010000
71
+
72
+ # Don't check for text files
73
+ NO_CHECK_TEXT = 0x020000
74
+
75
+ # Don't check for cdf files
76
+ NO_CHECK_CDF = 0x040000
77
+
78
+ # Don't check tokens
79
+ NO_CHECK_TOKENS = 0x100000
80
+
81
+ # Don't check text encodings
82
+ NO_CHECK_ENCODING = 0x200000
83
+
84
+ # alias for NO_CHECK_TEXT
85
+ NO_CHECK_ASCII = NO_CHECK_TEXT
86
+ end
87
+
88
+ # Returns the default magic database path.
89
+ def self.path
90
+ FFI::Libmagic.magic_getpath(nil, 0)
91
+ end
92
+
93
+ # A base class for other Magikku error types
94
+ class MagikkuError < StandardError
95
+ end
96
+
97
+ # Raised when an error occurs during loading of a magic database.
98
+ class DbLoadError < MagikkuError
99
+ end
100
+
101
+ # Raised when an unexpected fatal error occurs initializing libmagic
102
+ class InitFatal < MagikkuError
103
+ end
104
+
105
+ # Raised when an error occurs during compiling of a magic database.
106
+ class CompileError < MagikkuError
107
+ end
108
+
109
+ # Raised when an error occurs when setting flags on a Magikku object.
110
+ class FlagError < MagikkuError
111
+ end
112
+
113
+ # Raised when an error occurs when setting flags on a Magikku object.
114
+ class ClosedError < MagikkuError
115
+ end
116
+
117
+ # Initializes a new libmagic data scanner
118
+ #
119
+ # @param [Hash,nil] params
120
+ # A hash of parameters or nil for defaults.
121
+ #
122
+ # @option params [Fixnum,nil] :flags
123
+ # Optional flags to magic (see MagikkuFFI::Flags).
124
+ # The flag values should be 'or'ed to gether with '|'.
125
+ # Default: NONE
126
+ #
127
+ # @option params [String, nil] :db
128
+ # Optional magicfile databases or un-compiled magic files.
129
+ # Default: the default system magic.mgc file.
130
+ #
131
+ # @see See MagikkuFFI::Flags and libmagic(3) manpage
132
+ # @see dbload()
133
+ def initialize(param = nil)
134
+ param ||= {}
135
+ raise(TypeError, "Invalid Type for params") if not param.is_a?(Hash)
136
+
137
+ flags = param[:flags] || Flags::NONE
138
+ raise(TypeError, "flags must be a Fixnum") if not flags.is_a?(Fixnum)
139
+
140
+ db = param[:db]
141
+ raise(TypeError, "db must be nil or a String") if db and not db.is_a?(String)
142
+
143
+ @_cookie = FFI::Libmagic.magic_open(flags)
144
+ if @_cookie.null?
145
+ raise(InitFatal, "magic_open(#{flags}) returned a null pointer")
146
+ end
147
+
148
+ if FFI::Libmagic.magic_load(_cookie, db) != 0
149
+ err = lasterror()
150
+ FFI::Libmagic.magic_close(_cookie)
151
+ raise(DbLoadError, "Error loading db: #{db.inspect} " << err)
152
+ end
153
+ end
154
+
155
+ # Close the libmagic data scanner handle when you are finished with it
156
+ def close
157
+ FFI::Libmagic.magic_close(_cookie) unless @closed
158
+
159
+ @closed = true
160
+ return nil
161
+ end
162
+
163
+ def closed?
164
+ (@closed == true)
165
+ end
166
+
167
+ # Analyzes file contents against the magicfile database
168
+ #
169
+ # @param filename
170
+ # The path to a file to inspect
171
+ # @return [String]
172
+ # A textual description of the contents of the file
173
+ def file(filename)
174
+ File.stat(filename)
175
+ raise(TypeError, "filename must not be nil") if filename.nil?
176
+ FFI::Libmagic.magic_file(_cookie, filename)
177
+ end
178
+
179
+ # Analyzes a string buffer against the magicfile database
180
+ #
181
+ # @param filename
182
+ # The string buffer to inspect
183
+ # @return [String]
184
+ # A textual description of the contents the string
185
+ def string(str)
186
+ raise(TypeError, "wrong argument type #{str.class} (expected String)") unless str.is_a?(String)
187
+ p = FFI::MemoryPointer.new(str.size)
188
+ p.write_string_length(str, str.size)
189
+ FFI::Libmagic.magic_buffer(_cookie, p, str.size)
190
+ end
191
+
192
+ # Used to load one or more magic databases.
193
+ #
194
+ # @param magicfiles
195
+ # One or more filenames seperated by colons. If nil, the default database
196
+ # is loaded.
197
+ # If uncompiled magic files are specified, they are compiled on the fly
198
+ # but they do not generate new .mgc files as with the compile method.
199
+ # Multiple files be specified by seperating them with colons.
200
+ #
201
+ # @raise [DbLoadError] if an error occurred loading the database(s)
202
+ def dbload(magicfiles)
203
+ if FFI::Libmagic.magic_load(_cookie, magicfiles) != 0
204
+ raise(DbLoadError, "Error loading db: #{magicfiles.inspect} " << lasterror())
205
+ else
206
+ return true
207
+ end
208
+ end
209
+
210
+ # Sets flags for the magic analyzer handle.
211
+ #
212
+ # @param flags
213
+ # Flags to to set for magic. See MagikkuFFI::Flags and libmagic(3) manpage.
214
+ # The flag values should be 'or'ed together with '|'.
215
+ # Using 0 will clear all flags.
216
+ def flags=(flags)
217
+ if FFI::Libmagic.magic_setflags(_cookie, flags) < 0
218
+ raise(FlagError, lasterror())
219
+ end
220
+ end
221
+
222
+ # Can be used to compile magic files. This does not load files, however. You must
223
+ # use dbload for that.
224
+ #
225
+ # Note: Errors and warnings may be displayed on stderr.
226
+ #
227
+ # @param [String,nil] filename
228
+ # A colon seperated list of filenames or a single filename.
229
+ # The compiled files created are generated in the current directory using
230
+ # the basename(1) of each file argument with ".mgc" appended to it.
231
+ # Directory names can be compiled, in which case the contents of the directory
232
+ # will be compiled together as a single .mgc file.
233
+ # nil compiles the default database.
234
+ #
235
+ # @return [true] if everything went well.
236
+ #
237
+ # @raise [CompileError] if an error occurred.
238
+ def compile(filenames)
239
+ if FFI::Libmagic.magic_compile(_cookie, filenames) != 0
240
+ raise(CompileError, "Error compiling #{filenames.inspect}: " << lasterror())
241
+ else
242
+ return true
243
+ end
244
+ end
245
+
246
+ # Can be used to check the validity of magic files before compiling them.
247
+ # This is basically a dry-run that can be used before compiling magicfile
248
+ # databases.
249
+ #
250
+ # Note: Errors and warnings may be displayed on stderr.
251
+ #
252
+ # @param [String,nil] filename
253
+ # A colon seperated list of filenames or a single file. nil checks the
254
+ # default database.
255
+ #
256
+ # @return [true,false] Indicates whether the check was successful.
257
+ def check_syntax(filenames=nil)
258
+ return (FFI::Libmagic.magic_check(_cookie, filenames) == 0)
259
+ end
260
+
261
+ private
262
+ def _cookie
263
+ if @closed
264
+ raise(ClosedError, "This magic cookie is closed and can no longer be used")
265
+ else
266
+ @_cookie
267
+ end
268
+ end
269
+
270
+ def lasterror
271
+ FFI::Libmagic.magic_error(_cookie)
272
+ end
273
+
274
+ def lasterrno
275
+ FFI::Libmagic.magic_errno(_cookie)
276
+ end
277
+
278
+ end
@@ -0,0 +1,288 @@
1
+ require 'tmpdir'
2
+
3
+ shared_examples_for "Magikku compiling interface" do
4
+
5
+ context "Initializing" do
6
+ it "should initialize cleanly without arguments" do
7
+ c=nil
8
+ lambda { c=@klass.new }.should_not raise_error
9
+ c.close if c
10
+ end
11
+
12
+ it "should initialize cleanly when a flag argument is given" do
13
+ c=nil
14
+ lambda { c=@klass.new(:flags => @klass::Flags::NONE) }.should_not raise_error
15
+ c.close if c
16
+ end
17
+
18
+ it "should initialize cleanly when a magicfile argument is given" do
19
+ c=nil
20
+ lambda { c=@klass.new(:db => @testrule) }.should_not raise_error
21
+ c.close
22
+ end
23
+
24
+ it "should initialize cleanly when both arguments are given" do
25
+ c=nil
26
+ lambda {
27
+ c=@klass.new(:flags => @klass::Flags::NONE, :db => @testrule)
28
+ }.should_not raise_error
29
+ c.close if c
30
+ end
31
+
32
+
33
+ it "should raise an error when incorrect argument types are given" do
34
+ c=nil
35
+ lambda { c=@klass.new(nil) }.should_not raise_error()
36
+ c.close if c
37
+ c=nil
38
+ lambda { c=@klass.new(Object.new) }.should raise_error(TypeError)
39
+ c.close if c
40
+ end
41
+
42
+
43
+ it "should raise an error when the wrong argument count is given" do
44
+ c=nil
45
+ lambda { c=@klass.new(Hash.new,Object.new) }.should raise_error(ArgumentError)
46
+ c.close if c
47
+ end
48
+
49
+ it "should raise an error when a nonexistant magic file is given" do
50
+ c=nil
51
+ lambda {
52
+ c=@klass.new(:db => sample_file('totallybogus'))
53
+ }.should raise_error(@klass::DbLoadError)
54
+ c.close if c
55
+ end
56
+
57
+ it "should raise an error when a magic file format error occurs" do
58
+ c=nil
59
+ lambda {
60
+ c=@klass.new(:db => sample_file('fail_magicrules'))
61
+ }.should raise_error(@klass::DbLoadError)
62
+ c.close if c
63
+ end
64
+ end
65
+
66
+ context "An Instantiated Object" do
67
+ before :each do
68
+ @magic=@klass.new()
69
+ end
70
+
71
+ after :each do
72
+ @magic.close if not @magic.closed?
73
+ end
74
+
75
+ it "should be able to identify file buffers" do
76
+ @magic.file(sample_file("test.txt")).should == "ASCII text"
77
+ @magic.file(sample_file("test.c")).should == "ASCII C program text"
78
+ end
79
+
80
+ it "should be able to identify string buffers" do
81
+ @magic.string(File.read(sample_file("test.txt"))).should == "ASCII text"
82
+ @magic.string(File.read(sample_file("test.c"))).should == "ASCII C program text"
83
+ @magic.string("\x01\x02\x03\x04").should == "data"
84
+ end
85
+
86
+ it "should be able to use flag options" do
87
+ @magic.flags = @klass::Flags::MIME
88
+ @magic.string(File.read(sample_file("test.txt"))).should == "text/plain; charset=us-ascii"
89
+ @magic.string(File.read(sample_file("test.c"))).should == "text/x-c; charset=us-ascii"
90
+ @magic.string("\x01\x02\x03\x04").should == "application/octet-stream; charset=binary"
91
+ end
92
+
93
+ it "should raise an error if a nonexistant file is specified to inspect" do
94
+ lambda{
95
+ @magic.file(sample_file('totallybogusfile'))
96
+ }.should raise_error(Errno::ENOENT)
97
+ end
98
+
99
+ it "should raise an error when an incorrect object is passed to file()" do
100
+ lambda{ @magic.file(Object.new) }.should raise_error(TypeError)
101
+ lambda{ @magic.file(nil) }.should raise_error(TypeError)
102
+ end
103
+
104
+ it "should raise an error when an incorrect object is passed to file()" do
105
+ lambda{ @magic.string(Object.new) }.should raise_error(TypeError)
106
+ lambda{ @magic.string(nil) }.should raise_error(TypeError)
107
+ end
108
+
109
+ it "should be able to load magicrules databases" do
110
+ test_str="THISISARUBYMAGICTEST blah blah\nblah\x01\x02"
111
+ @magic.string(test_str).should_not == "RUBYMAGICTESTHIT"
112
+ @magic.dbload(sample_file('test_magicrule'))
113
+ @magic.string(test_str).should == "RUBYMAGICTESTHIT"
114
+ end
115
+
116
+ it "should load the default magicrules database when nil is loaded" do
117
+ test_str="THISISARUBYMAGICTEST blah blah\nblah\x01\x02"
118
+ @magic.string(test_str).should_not == "RUBYMAGICTESTHIT"
119
+ @magic.string(test_str).should == "data"
120
+ @magic.dbload(nil)
121
+ @magic.string(test_str).should_not == "RUBYMAGICTESTHIT"
122
+ @magic.string(test_str).should == "data"
123
+ end
124
+
125
+ it "should raise an error when loading an invalid filename" do
126
+ lambda{
127
+ @magic.dbload(sample_file('totallybogusfile'))
128
+ }.should raise_error(@klass::DbLoadError)
129
+ end
130
+
131
+ it "should raise an error when an incorrect object is passed to dbload()" do
132
+ lambda{
133
+ @magic.dbload(Object.new)
134
+ }.should raise_error(@argerror)
135
+ end
136
+
137
+ it "should be able to be closed" do
138
+ lambda { @magic.close }.should_not raise_error
139
+ @magic.should be_closed
140
+ end
141
+
142
+ it "should correctly indicate whether it is already closed" do
143
+ @magic.should_not be_closed
144
+ @magic.close
145
+ @magic.should be_closed
146
+ lambda { @magic.close }.should_not raise_error
147
+ end
148
+
149
+ it "should not be usable after it is closed" do
150
+ @magic.close
151
+ lambda{
152
+ @magic.file(File.expand_path(__FILE__))
153
+ }.should raise_error(@klass::ClosedError)
154
+
155
+ lambda{
156
+ @magic.string("foo")
157
+ }.should raise_error(@klass::ClosedError)
158
+
159
+ lambda{
160
+ @magic.check_syntax(sample_file('fail_magicrules'))
161
+ }.should raise_error(@klass::ClosedError)
162
+
163
+ lambda{
164
+ @magic.compile(sample_file('fail_magicrules'))
165
+ }.should raise_error(@klass::ClosedError)
166
+
167
+ lambda{
168
+ @magic.check_syntax(sample_file('ruby_magicrules'))
169
+ }.should raise_error(@klass::ClosedError)
170
+
171
+ lambda{
172
+ @magic.compile(sample_file('ruby_magicrules'))
173
+ }.should raise_error(@klass::ClosedError)
174
+
175
+ end
176
+ end
177
+
178
+ context "Compiling And Convenience Methods" do
179
+ before :all do
180
+ @tmpdir = Dir.mktmpdir
181
+ end
182
+
183
+ before :each do
184
+ @origdir =Dir.pwd
185
+ Dir.chdir @tmpdir
186
+
187
+ @testrule = sample_file("ruby_magicrules")
188
+ @expect_mgc = "#{File.basename(@testrule)}.mgc"
189
+
190
+ File.should_not be_file(@expect_mgc)
191
+ end
192
+
193
+ after :each do
194
+ File.delete @expect_mgc if File.exists?(@expect_mgc)
195
+ Dir.chdir @origdir
196
+ end
197
+
198
+ it "should work using the compile convenience class method" do
199
+ @klass.compile(@testrule).should == true
200
+ end
201
+
202
+ it "should work using an instantiated object" do
203
+ m=@klass.new
204
+ m.compile(@testrule).should == true
205
+ m.close
206
+ end
207
+
208
+ it "should raise an error when compiling an invalid filename" do
209
+ lambda{
210
+ @klass.compile(sample_file('totallnonexistantfile'))
211
+ }.should raise_error(@klass::CompileError)
212
+ end
213
+
214
+ it "should raise an error when compiling an syntactically incorrect filename" do
215
+ lambda{
216
+ @klass.compile(sample_file('fail_magicrules'))
217
+ }.should raise_error(@klass::CompileError)
218
+ end
219
+
220
+ it "should work using the check_syntax convenience class method" do
221
+ @klass.check_syntax(@testrule).should == true
222
+ File.should_not be_file(@expect_mgc)
223
+ @klass.check_syntax(@testrule).should == true
224
+ @klass.check_syntax(sample_file('fail_magicrules')).should == false
225
+ end
226
+
227
+ it "should work when checking syntax using an instantiated object" do
228
+ m=@klass.new
229
+ m.check_syntax(@testrule).should == true
230
+ File.should_not be_file(@expect_mgc)
231
+ m.check_syntax(sample_file('fail_magicrules')).should == false
232
+ m.close
233
+ end
234
+
235
+ it "should return false when checking an invalid filename" do
236
+ @klass.check_syntax(sample_file('totallnonexistantfile')).should == false
237
+ m=@klass.new
238
+ m.check_syntax(sample_file('totallnonexistantfile')).should == false
239
+ m.close
240
+ end
241
+
242
+ it "should check the default database when given a nil filename" do
243
+ @klass.check_syntax(nil).should == true
244
+ m=@klass.new
245
+ m.check_syntax(nil).should == true
246
+ m.close
247
+ end
248
+
249
+ it "should return the default magic database with path()" do
250
+ @klass.path.should be_kind_of(String)
251
+ @klass.path.should_not be_empty
252
+ end
253
+
254
+ it "should scan a file using the #{@klass}.file singleton method" do
255
+ @klass.file(sample_file('test.txt')).should == "ASCII text"
256
+ @klass.file(sample_file('test.c')).should == "ASCII C program text"
257
+ end
258
+
259
+ it "should take initialization params as extra args to .file()" do
260
+ @klass.file(sample_file('test.txt'),
261
+ :flags => @klass::Flags::MIME).should == "text/plain; charset=us-ascii"
262
+
263
+ @klass.file(sample_file('rawtestdat.raw'), :db => nil).should == "data"
264
+
265
+ @klass.file(sample_file('rawtestdat.raw'), :db => sample_file('test_magicrule')).should == "RUBYMAGICTESTHIT"
266
+
267
+ end
268
+
269
+ it "should scan a file using the #{@klass}.string singleton method" do
270
+ @klass.string(File.read(sample_file('test.txt'))).should == "ASCII text"
271
+ @klass.string(File.read(sample_file('test.c'))).should == "ASCII C program text"
272
+ end
273
+
274
+ it "should take initialization params as extra args to .string()" do
275
+
276
+ @klass.string(File.read(sample_file('test.txt')),
277
+ :flags => @klass::Flags::MIME).should == "text/plain; charset=us-ascii"
278
+ test_str="THISISARUBYMAGICTEST blah blah\nblah\x01\x02"
279
+ @klass.string(test_str).should_not == "RUBYMAGICTESTHIT"
280
+
281
+ @klass.string(test_str, :db => sample_file('test_magicrule')).should == "RUBYMAGICTESTHIT"
282
+
283
+ end
284
+
285
+
286
+ end
287
+
288
+ end
@@ -0,0 +1,30 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'magikku_ffi'
3
+
4
+ if Magikku != MagikkuFFI
5
+
6
+ describe MagikkuFFI do
7
+ before :all do
8
+ @klass = MagikkuFFI
9
+ @argerror = ArgumentError
10
+ end
11
+
12
+ it_should_behave_like "Magikku compiling interface"
13
+ end
14
+
15
+ describe MagikkuFFI::Flags do
16
+ context "Checking Values" do
17
+ Magikku::Flags.constants.each do |const|
18
+ it "should have the correct value for #{const}" do
19
+ Magikku::Flags.const_get(const).should == MagikkuFFI::Flags.const_get(const)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ else
25
+ describe "C Binding" do
26
+ it "was not compiled" do
27
+ pending "unable to test C bindings"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Magikku do
4
+ before :all do
5
+ @klass = Magikku
6
+ @argerror = TypeError
7
+ end
8
+
9
+ it_should_behave_like "Magikku compiling interface"
10
+ end
@@ -0,0 +1 @@
1
+ bogus
@@ -0,0 +1,2 @@
1
+ THISISARUBYMAGICTEST blah blah
2
+ blah
@@ -0,0 +1,16 @@
1
+
2
+ #------------------------------------------------------------------------------
3
+ # $File: ruby,v 1.3 2009/09/19 16:28:12 christos Exp $
4
+ # ruby: file(1) magic for Ruby scripting language
5
+ # URL: http://www.ruby-lang.org/
6
+ # From: Reuben Thomas <rrt@sc3d.org>
7
+
8
+ # Ruby scripts
9
+ 0 search/1/w #!\ /usr/bin/ruby Ruby script text executable
10
+ !:mime text/x-ruby
11
+ 0 search/1/w #!\ /usr/local/bin/ruby Ruby script text executable
12
+ !:mime text/x-ruby
13
+ 0 search/1 #!/usr/bin/env\ ruby Ruby script text executable
14
+ !:mime text/x-ruby
15
+ 0 search/1 #!\ /usr/bin/env\ ruby Ruby script text executable
16
+ !:mime text/x-ruby
@@ -0,0 +1,6 @@
1
+
2
+ /* this is a sample C program for testing libmagic */
3
+ int
4
+ main(int argc, char *argv) {
5
+ return 0;
6
+ }
@@ -0,0 +1,3 @@
1
+
2
+ This is a sample text file for testing libmagic.
3
+
@@ -0,0 +1 @@
1
+ 0 string THISISARUBYMAGICTEST RUBYMAGICTESTHIT
data/spec/spec.opts ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format nested
3
+ -b
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'magikku'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ require 'magikku_behaviors'
8
+
9
+ def sample_file(filename)
10
+ return File.expand_path(File.join(File.dirname(__FILE__), "sample", filename))
11
+ end
12
+
13
+ Spec::Runner.configure do |config|
14
+
15
+ end