magikku 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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