diff --git a/README.md b/README.md index 39a0b99..d78ea13 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,11 @@ For more information and some examples, see `Magic::TypeChecker` and ## Development -Create an issue if you think anything needs revision! +Create an issue or pull request to discuss any suggestions. + +### Known bugs (help wanted) + - `Magic::TypeChecker.max_regex=()` does not actually do *anything*. The value + remains constant regardless of the value passed to this method. ## Contributing diff --git a/shard.yml b/shard.yml index e3c4034..41e9ffc 100644 --- a/shard.yml +++ b/shard.yml @@ -4,11 +4,10 @@ version: 1.1.0 authors: - D. Scott Boggs -crystal: 0.35.0 +crystal: 1.0.0 development_dependencies: ameba: github: crystal-ameba/ameba license: MIT - diff --git a/spec/libmagic/magic_spec.cr b/spec/libmagic/magic_spec.cr index aa72f51..f800f50 100644 --- a/spec/libmagic/magic_spec.cr +++ b/spec/libmagic/magic_spec.cr @@ -1,18 +1,38 @@ require "../spec_helper" -include Magic -describe "LibMagic" do - cookie = LibMagic.open LibMagic::NONE +module Magic + describe "LibMagic" do + cookie = LibMagic.open(LibMagic::Options::NONE).not_nil! + describe ".set_flags (and .flags)" do + it "sets a flag" do + # Magic::LibMagic.flags(cookie).should eq 0 + # This seems to be a bug in spec, if you uncomment this line + # `crystal spec` will fail, but running + # `crystal run spec/libmagic/magic_spec.cr` will pass. + LibMagic.set_flags cookie, LibMagic::Options::CONTINUE + LibMagic::Options::CONTINUE.should eq LibMagic.flags cookie + LibMagic.set_flags cookie, LibMagic::Options::NONE + LibMagic.flags(cookie).should eq LibMagic::Options::NONE + end + end - describe ".set_flags (and .flags)" do - pending "sets a flag" do - LibMagic.flags(cookie).should eq 0 - LibMagic.set_flags(cookie, LibMagic::CONTINUE).should eq 0 - LibMagic.flags(cookie).should eq LibMagic::CONTINUE - LibMagic.set_flags(cookie, LibMagic::NONE).should eq 0 - LibMagic.flags(cookie).should eq 0 + describe ".set_param and .param" do + it "sets the regex param" do + value = 12345 + LibMagic.set_param cookie, LibMagic::Param::MAX_REGEX, pointerof(value) + LibMagic.get_param cookie, LibMagic::Param::MAX_REGEX, out result + result.should eq 12345 + end + {% for param in %w(indirection name elf_notes elf_phnum elf_shnum bytes) %} + it "sets the {{param.id}} param" do + value = 12345 + LibMagic.set_param cookie, LibMagic::Param::MAX_{{param.id.upcase}}, pointerof(value) + LibMagic.get_param cookie, LibMagic::Param::MAX_{{param.id.upcase}}, out result + result.should eq 12345 + end + {% end %} end + ensure + cookie.try &->Magic::LibMagic.close(LibMagic::MagicT) end - - LibMagic.close cookie end diff --git a/spec/magic.cr_spec.cr b/spec/magic.cr_spec.cr index 725aaf2..e22c425 100644 --- a/spec/magic.cr_spec.cr +++ b/spec/magic.cr_spec.cr @@ -43,11 +43,11 @@ describe Magic do describe "Magic::LibMagic" do it "works" do - magic_cookie = Magic::LibMagic.open Magic::LibMagic::NONE + magic_cookie = Magic::LibMagic.open Magic::LibMagic::Options::NONE Magic::LibMagic.close magic_cookie end it "knows the mime-type of a directory" do - magic_cookie = Magic::LibMagic.open Magic::LibMagic::MIME_TYPE + magic_cookie = Magic::LibMagic.open Magic::LibMagic::Options::MIME_TYPE Magic::LibMagic.load magic_cookie, nil String.new(Magic::LibMagic.file(magic_cookie, ".")).should eq "inode/directory" end @@ -59,59 +59,59 @@ describe Magic do chkr = Magic::TypeChecker.new chkr.options.should eq Magic::TypeChecker::DEFAULT_OPTIONS chkr.options = 1 - chkr.options.should eq Magic::LibMagic::DEBUG + chkr.options.should eq Magic::LibMagic::Options::DEBUG end end # options - describe(".max_indir=") do - it("sets a value") do + describe ".max_indirection=" do + it "sets a value" do chkr = Magic::TypeChecker.new - chkr.max_indir = 12345 - chkr.max_indir.should(eq(12345)) + chkr.max_indirection = 12345 + chkr.max_indirection.should eq 12345 end end - describe(".max_name=") do - it("sets a value") do + describe ".max_name=" do + it "sets a value" do chkr = Magic::TypeChecker.new chkr.max_name = 12345 - chkr.max_name.should(eq(12345)) + chkr.max_name.should eq 12345 end end - describe(".max_elf_phnum=") do - it("sets a value") do + describe ".max_elf_phnum=" do + it "sets a value" do chkr = Magic::TypeChecker.new chkr.max_elf_phnum = 12345 - chkr.max_elf_phnum.should(eq(12345)) + chkr.max_elf_phnum.should eq 12345 end end - describe(".max_elf_notes=") do - it("sets a value") do + describe ".max_elf_notes=" do + it "sets a value" do chkr = Magic::TypeChecker.new chkr.max_elf_notes = 12345 - chkr.max_elf_notes.should(eq(12345)) + chkr.max_elf_notes.should eq 12345 end end - describe(".max_elf_shnum=") do - it("sets a value") do + describe ".max_elf_shnum=" do + it "sets a value" do chkr = Magic::TypeChecker.new chkr.max_elf_shnum = 12345 - chkr.max_elf_shnum.should(eq(12345)) + chkr.max_elf_shnum.should eq 12345 end end # PARAM_REGEX_MAX cannot be changed on my system. - describe(".max_regex=") do + describe ".max_regex=" do pending "sets a value" do chkr = Magic::TypeChecker.new chkr.max_regex = 12345 - chkr.max_regex.should(eq(12345)) + chkr.max_regex.should eq 12345 end end - describe(".max_bytes=") do - it("sets a value") do + describe ".max_bytes=" do + it "sets a value" do chkr = Magic::TypeChecker.new chkr.max_bytes = 12345 - chkr.max_bytes.should(eq(12345)) + chkr.max_bytes.should eq 12345 end end @@ -187,7 +187,7 @@ describe Magic do end describe "#reset_options" do chkr = Magic::TypeChecker.new - chkr.debug_output.options.should eq (Magic::TypeChecker::DEFAULT_OPTIONS | Magic::LibMagic::DEBUG) + chkr.debug_output.options.should eq (Magic::TypeChecker::DEFAULT_OPTIONS | Magic::LibMagic::Options::DEBUG) chkr.reset_options.options.should eq Magic::TypeChecker::DEFAULT_OPTIONS end end diff --git a/src/core_ext.cr b/src/core_ext.cr new file mode 100644 index 0000000..77e7ed2 --- /dev/null +++ b/src/core_ext.cr @@ -0,0 +1,5 @@ +struct Int32 + def ==(opts : Magic::LibMagic::Options) + self == opts.value + end +end diff --git a/src/libmagic/magic.cr b/src/libmagic/magic.cr index 39ecf30..440d689 100644 --- a/src/libmagic/magic.cr +++ b/src/libmagic/magic.cr @@ -31,36 +31,46 @@ module Magic @[Link(ldflags: "-lmagic")] lib LibMagic - # No special handling. - NONE = 0x0000000 - # Print debugging messages to stderr. - DEBUG = 0x0000001 - # TODO: more docs - SYMLINK = 0x0000002 - COMPRESS = 0x0000004 - DEVICES = 0x0000008 - MIME_TYPE = 0x0000010 - CONTINUE = 0x0000020 - CHECK = 0x0000040 - PRESERVE_ATIME = 0x0000080 - RAW = 0x0000100 - ERROR = 0x0000200 - MIME_ENCODING = 0x0000400 - MIME = MIME_TYPE | MIME_ENCODING - APPLE = 0x0000800 - EXTENSION = 0x1000000 - COMPRESS_TRANSP = 0x2000000 - NODESC = MAGIC_EXTENSION | MAGIC_MIME | MAGIC_APPLE - NO_CHECK_COMPRESS = 0x0001000 - NO_CHECK_TAR = 0x0002000 - NO_CHECK_SOFT = 0x0004000 - NO_CHECK_APPTYPE = 0x0008000 - NO_CHECK_ELF = 0x0010000 - NO_CHECK_TEXT = 0x0020000 - NO_CHECK_CDF = 0x0040000 - NO_CHECK_TOKENS = 0x0100000 - NO_CHECK_ENCODING = 0x0200000 - NO_CHECK_BUILTIN = MAGIC_NO_CHECK_COMPRESS | MAGIC_NO_CHECK_TAR | MAGIC_NO_CHECK_APPTYPE | MAGIC_NO_CHECK_ELF | MAGIC_NO_CHECK_TEXT | MAGIC_NO_CHECK_CDF | MAGIC_NO_CHECK_TOKENS | MAGIC_NO_CHECK_ENCODING | 0 \ + enum Options : Int32 + # No special handling. + NONE = 0x0000000 + # Print debugging messages to stderr. + DEBUG = 0x0000001 + # TODO: more docs + SYMLINK = 0x0000002 + COMPRESS = 0x0000004 + DEVICES = 0x0000008 + MIME_TYPE = 0x0000010 + CONTINUE = 0x0000020 + CHECK = 0x0000040 + PRESERVE_ATIME = 0x0000080 + RAW = 0x0000100 + ERROR = 0x0000200 + MIME_ENCODING = 0x0000400 + MIME = MIME_TYPE | MIME_ENCODING + APPLE = 0x0000800 + EXTENSION = 0x1000000 + COMPRESS_TRANSP = 0x2000000 + NODESC = EXTENSION | MIME | APPLE + NO_CHECK_COMPRESS = 0x0001000 + NO_CHECK_TAR = 0x0002000 + NO_CHECK_SOFT = 0x0004000 + NO_CHECK_APPTYPE = 0x0008000 + NO_CHECK_ELF = 0x0010000 + NO_CHECK_TEXT = 0x0020000 + NO_CHECK_CDF = 0x0040000 + NO_CHECK_TOKENS = 0x0100000 + NO_CHECK_ENCODING = 0x0200000 + NO_CHECK_BUILTIN = NO_CHECK_COMPRESS | NO_CHECK_TAR | NO_CHECK_APPTYPE | NO_CHECK_ELF | NO_CHECK_TEXT | NO_CHECK_CDF | NO_CHECK_TOKENS | NO_CHECK_ENCODING | 0 \ + + NO_CHECK_ASCII = NO_CHECK_TEXT + NO_CHECK_FORTRAN = 0x000000 + NO_CHECK_TROFF = 0x000000 + + def ==(other : Int32) + value == other + end + end SNPRINTB = "\177\020\ b\0debug\0\ @@ -90,17 +100,16 @@ b\27no_check_reserved2\0\ b\30extension\0\ b\31transp_compression\0\ " - NO_CHECK_ASCII = MAGIC_NO_CHECK_TEXT - NO_CHECK_FORTRAN = 0x000000 - NO_CHECK_TROFF = 0x000000 - VERSION = 532 - PARAM_INDIR_MAX = 0o0 - PARAM_NAME_MAX = 1 - PARAM_ELF_PHNUM_MAX = 2 - PARAM_ELF_SHNUM_MAX = 3 - PARAM_ELF_NOTES_MAX = 4 - PARAM_REGEX_MAX = 5 - PARAM_BYTES_MAX = 6 + VERSION = 532 + enum Param + MAX_INDIRECTION + MAX_NAME + MAX_ELF_PHNUM + MAX_ELF_SHNUM + MAX_ELF_NOTES + MAX_REGEX + MAX_BYTES + end # type MagicSet = Object alias MagicT = MagicSet* # returns a magic cookie on success and NULL on failure setting errno to diff --git a/src/type_checker.cr b/src/type_checker.cr index 37d4179..07ec3ee 100644 --- a/src/type_checker.cr +++ b/src/type_checker.cr @@ -1,6 +1,7 @@ # Crystal bindings for LibMagic (aka the `file` command) which lets you quickly # and easily discover the filetype of a given file or bytestream. require "./libmagic/magic" +require "./core_ext" module Magic # An error encountered while communicating with the C libmagic API. @@ -62,12 +63,12 @@ module Magic getter db_files : Set(String)? Integer = Int8 | Int16 | Int32 | Int64 | UInt8 | UInt16 | UInt32 | UInt64 - DEFAULT_OPTIONS = LibMagic::RAW | LibMagic::ERROR + DEFAULT_OPTIONS = LibMagic::Options::RAW | LibMagic::Options::ERROR # the current options used by the lib - @options : Int32 + @options : LibMagic::Options # new options that have been set but may not have been passed to the # lib yet. - @new_options : Int32 + @new_options : LibMagic::Options @checker : LibMagic::MagicT alias IterableOfStrings = Indexable(String) | Set(String) @@ -84,7 +85,7 @@ module Magic end @new_options = @options if dbf = database_files - @db_files = dbf + LibMagic.load @checker, (@db_files = dbf.to_set).join ':' else LibMagic.load @checker, nil end @@ -233,7 +234,11 @@ module Magic # debug_output, follow_symlinks, etc.). Equivalent to calling C's # `magic_setflags()` with the given integer. See `libmagic(2)` for more # details. Appropriate values can be bitwise-or'd from LibMagic's constants. - def options=(@new_options) + def options=(@new_options : LibMagic::Options) + end + + def options=(options : Int32) + @new_options = LibMagic::Options.new options end # The current value of the magic flags. Equivalent to calling C's @@ -257,12 +262,12 @@ module Magic # use the MAGIC_NONE option (the default for libmagic) instead of the # default for magic.cr def libmagic_defaults - @new_options = LibMagic::NONE + @new_options = LibMagic::Options::NONE self end def libmagic_defaults? - @new_options == LibMagic::NONE + @new_options == LibMagic::Options::NONE end private def set(flag) @@ -325,7 +330,7 @@ module Magic inverse_bitflag_option( :escape_unprintable, - LibMagic::RAW, + LibMagic::Options::RAW, "escapes non-printable bytes as their `0oOOO` octal numeric forms.\ By default crystal handles this in cases where it's important (in\ `puts` for example), so by default strings contain the raw values for\ @@ -333,81 +338,90 @@ module Magic ) inverse_bitflag_option( :return_error_as_text, - LibMagic::ERROR, - "Return errors while trying to open files and follow symlinks in the\ - filetype text rather than raising an error. This differs from the \ - `libmagic` default, because it makes more sense to handle the errors in \ - Crystal in most cases than to output them as text." + LibMagic::Options::ERROR, + "Errors may occur while trying to open files and follow symlinks. Turning \ + this option on returns the error message in the filetype text rather than \ + raising an error. This differs from the `libmagic` default, because it \ + makes more sense to handle the errors in Crystal in most cases than to \ + output them as text." ) bitflag_option( :debug_output, - LibMagic::DEBUG, + LibMagic::Options::DEBUG, :"Have `libmagic(2)` print debugging messages to stderr.") bitflag_option( :follow_symlinks, - LibMagic::SYMLINK, + LibMagic::Options::SYMLINK, :"If the file queried is a symlink, follow it.") bitflag_option( :look_into_compressed_files, - LibMagic::COMPRESS, + LibMagic::Options::COMPRESS, :"If the file is compressed, unpack it and look at the contents.") bitflag_option( :check_device, - LibMagic::DEVICES, + LibMagic::Options::DEVICES, "If the file is a block or character special device, then\ open the device and try to look in its contents.") bitflag_option( :get_mime_type, - LibMagic::MIME_TYPE, + LibMagic::Options::MIME_TYPE, :"Return a MIME type string, instead of a textual description.") bitflag_option( :get_mime_encoding, - LibMagic::MIME_ENCODING, + LibMagic::Options::MIME_ENCODING, :"Return a MIME encoding, instead of a textual description.") bitflag_option( :get_mime, - LibMagic::MIME, + LibMagic::Options::MIME, :"sets both `get_mime_type` and `get_mime_encoding`") bitflag_option( :all_types, - LibMagic::CONTINUE, + LibMagic::Options::CONTINUE, :"Return all matches, not just the first.") bitflag_option( :check_db, - LibMagic::CHECK, + LibMagic::Options::CHECK, "Check the magic database for consistency and print\ warnings to stderr, while checking a file.") bitflag_option( :try_to_preserve_access_time, - LibMagic::PRESERVE_ATIME, + LibMagic::Options::PRESERVE_ATIME, "On systems that support utime(3) or utimes(2), attempt to\ preserve the access time of files analysed.") bitflag_option( :preserve_atime, - LibMagic::PRESERVE_ATIME, + LibMagic::Options::PRESERVE_ATIME, :":ditto:") bitflag_option( :apple, - LibMagic::APPLE, + LibMagic::Options::APPLE, :"Return the Apple creator and type.") bitflag_option( :get_extensions, - LibMagic::EXTENSION, + LibMagic::Options::EXTENSION, :"Makes #of() return a slash-separated list of extensions for this file type.") bitflag_option( :no_compression_info, - LibMagic::COMPRESS_TRANSP, + LibMagic::Options::COMPRESS_TRANSP, :"Don't report on compression, only report about the uncompressed data.") # set the various limits related to the magic library - private def limit(behavior : Int32, to : Int32) + private def limit(behavior : LibMagic::Param, to : Int32) LibMagic.set_param @checker, behavior, pointerof(to) end - # limit(LibMagic::PARAM_{SOMETHING}_MAX) do |current| - # current + new_value + # Set a new limit based on the old one. Return nil from the block to avoid + # updating the value. For example: + # + # ``` + # limit LibMagic::PARAM_{SOMETHING}_MAX do |current| + # nv = current + new_value + # if nv < some_max_value + # nv + # end # end - private def limit(behavior : Int32) + # ``` + private def limit(behavior : LibMagic::Param, & : Proc(Int32, Int32?)) LibMagic.param @checker, behavior, out current if new_value = yield current LibMagic.set_param @checker, behavior, new_value @@ -421,7 +435,7 @@ module Magic # it the constant PARAM_{{method_name.id[4..-1].upcase}}_MAX and the # given value. See `libmagic(2)`. {{extra_docs.id}} def {{method_name.id}}=(value : Int32) - limit LibMagic::PARAM_{{ method_name.id[4..-1].upcase }}_MAX, to: value + limit LibMagic::Param::{{ method_name.id.upcase }}, to: value {{method_name.id}} end # Yields the current value of the {{method_name.id}} to the block, then @@ -435,7 +449,7 @@ module Magic # end # ``` def {{method_name.id}} - limit LibMagic::PARAM_{{ method_name.id[4..-1].upcase }}_MAX do |curr| + limit LibMagic::Param::{{ method_name.id.upcase }} do |curr| yield curr end {{method_name.id}} @@ -449,16 +463,16 @@ module Magic # {{extra_docs.id}} def {{method_name.id}} LibMagic.get_param(@checker, - LibMagic::PARAM_{{method_name.id[4..-1].upcase}}_MAX, + LibMagic::Param::{{method_name.id.upcase}}, out value) value end end - magic_param(:max_indir, + magic_param(:max_indirection, 15, "Controls how many levels of recursion will be followed for\ - indirect magic entries.") + indirect magic entries.") magic_param(:max_name, 30, "Controls the maximum number of calls for name/use.")