1 module magic;
2 
3 private import core.stdc.stddef; // for size_t
4 private import std.string;
5 
6 extern (C) {
7 @system:
8 nothrow:
9 
10 alias magic_t = void *;
11 
12 magic_t magic_open(int flags);
13 void magic_close(magic_t ms);
14 
15 immutable(char) *magic_getpath(in char *path, int flags);
16 immutable(char) *magic_file(magic_t ms, in char *path);
17 immutable(char) *magic_descriptor(magic_t ms, int fd);
18 immutable(char) *magic_buffer(magic_t ms, in void *buffer, size_t length);
19 
20 immutable(char) *magic_error(magic_t ms);
21 int magic_setflags(magic_t ms, int flags);
22 
23 int magic_version();
24 int magic_load(magic_t ms, in char *path);
25 int magic_compile(magic_t ms, in char *path);
26 int magic_check(magic_t ms, in char *path);
27 int magic_list(magic_t ms, in char *path);
28 int magic_errno(magic_t ms);
29 
30 
31 enum
32 	MAGIC_NONE              = 0x000000, /// No flags
33 	MAGIC_DEBUG             = 0x000001, /// Turn on debugging
34 	MAGIC_SYMLINK           = 0x000002, /// Follow symlinks
35 	MAGIC_COMPRESS          = 0x000004, /// Check inside compressed files
36 	MAGIC_DEVICES           = 0x000008, /// Look at the contents of devices
37 	MAGIC_MIME_TYPE         = 0x000010, /// Return the MIME type
38 	MAGIC_CONTINUE          = 0x000020, /// Return all matches
39 	MAGIC_CHECK             = 0x000040, /// Print warnings to stderr
40 	MAGIC_PRESERVE_ATIME    = 0x000080, /// Restore access time on exit
41 	MAGIC_RAW               = 0x000100, /// Don't translate unprintable chars
42 	MAGIC_ERROR             = 0x000200, /// Handle ENOENT etc as real errors
43 	MAGIC_MIME_ENCODING     = 0x000400, /// Return the MIME encoding
44 	MAGIC_APPLE             = 0x000800; /// Return the Apple creator and type
45 
46 
47 enum
48 	MAGIC_MIME              = MAGIC_MIME_TYPE | MAGIC_MIME_ENCODING;
49 
50 
51 enum
52 	MAGIC_NO_CHECK_COMPRESS = 0x001000, /// Don't check for compressed files
53 	MAGIC_NO_CHECK_TAR      = 0x002000, /// Don't check for tar files
54 	MAGIC_NO_CHECK_SOFT     = 0x004000, /// Don't check magic entries
55 	MAGIC_NO_CHECK_APPTYPE  = 0x008000, /// Don't check application type
56 	MAGIC_NO_CHECK_ELF      = 0x010000, /// Don't check for elf details
57 	MAGIC_NO_CHECK_TEXT     = 0x020000, /// Don't check for text files
58 	MAGIC_NO_CHECK_CDF      = 0x040000, /// Don't check for cdf files
59 	MAGIC_NO_CHECK_TOKENS   = 0x100000, /// Don't check tokens
60 	MAGIC_NO_CHECK_ENCODING = 0x200000; /// Don't check text encodings
61 
62 
63 /++ No built-in tests; only consult the magic file +/
64 enum
65 	MAGIC_NO_CHECK_BUILTIN  = MAGIC_NO_CHECK_COMPRESS
66 	                        | MAGIC_NO_CHECK_TAR
67 	                     // | MAGIC_NO_CHECK_SOFT
68 	                        | MAGIC_NO_CHECK_APPTYPE
69 	                        | MAGIC_NO_CHECK_ELF
70 	                        | MAGIC_NO_CHECK_TEXT
71 	                        | MAGIC_NO_CHECK_CDF
72 	                        | MAGIC_NO_CHECK_TOKENS
73 	                        | MAGIC_NO_CHECK_ENCODING;
74 
75 
76 /++ Defined for backwards compatibility (renamed) +/
77 enum
78 	MAGIC_NO_CHECK_ASCII    = MAGIC_NO_CHECK_TEXT;
79 
80 
81 /++ Defined for backwards compatibility; do nothing +/
82 enum
83 	MAGIC_NO_CHECK_FORTRAN  = MAGIC_NONE, /// Don't check ascii/fortran
84 	MAGIC_NO_CHECK_TROFF    = MAGIC_NONE; /// Don't check ascii/troff
85 
86 enum
87 	MAGIC_VERSION = 516; /// This implementation
88 }
89 
90 class MagicOpenFail: Error {
91 	@safe pure nothrow this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
92 	{
93 		super("Failed to create magic cookie", file, line, next);
94 	}
95 }
96 
97 class Magic {
98 protected:
99 	magic_t m;
100 
101 public:
102 	this(int flags = MAGIC_MIME_TYPE | MAGIC_NO_CHECK_BUILTIN) {
103 		m = magic_open(flags);
104 
105 		if (!m)
106 			throw new MagicOpenFail();
107 	}
108 
109 	~this() {
110 		magic_close(m);
111 	}
112 
113 	bool setflags(int flags) {
114 		return magic_setflags(m, flags) == 0;
115 	}
116 
117 
118 	@property string error() {
119 		return fromStringz(magic_error(m));
120 	}
121 
122 	@property int errno() {
123 		return magic_errno(m);
124 	}
125 
126 
127 	string file(in string path) {
128 		return fromStringz(magic_file(m, toStringz(path)));
129 	}
130 
131 	string descriptor(int fd) {
132 		return fromStringz(magic_descriptor(m, fd));
133 	}
134 
135 	string buffer(in void *buffer, size_t length) {
136 		return fromStringz(magic_buffer(m, buffer, length));
137 	}
138 
139 	string buffer(T)(in T buffer[]) {
140 		return fromStringz(magic_buffer(m, buffer.ptr, buffer.sizeof));
141 	}
142 
143 
144 	bool load(in string path = null) {
145 		const char *p = path ? toStringz(path) : null;
146 		return magic_load(m, p) == 0;
147 	}
148 
149 	bool compile(in string path = null) {
150 		const char *p = path ? toStringz(path) : null;
151 		return magic_compile(m, p) == 0;
152 	}
153 
154 	bool check(in string path = null) {
155 		const char *p = path ? toStringz(path) : null;
156 		return magic_check(m, p) == 0;
157 	}
158 
159 	bool list(in string path = null) {
160 		const char *p = path ? toStringz(path) : null;
161 		return magic_list(m, p) == 0;
162 	}
163 }
164 
165 unittest {
166 	import std.path: getcwd, buildPath;
167 	import std.stdio: File;
168 
169 	auto sample_dir = buildPath(getcwd(), "test_assets");
170 	auto sample_file = buildPath(getcwd(), "test_assets", "sample200.png");
171 
172 	ubyte[] sample_rar = [
173 		0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00, 0xcf,
174 		0x90, 0x73, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
175 	];
176 
177 	auto f = new File(sample_file, "rb");
178 
179 	auto m = new Magic();
180 	m.load();
181 
182 	assert(m.file(sample_dir) == "inode/directory");
183 	assert(m.file(sample_file) == "image/png");
184 	assert(m.descriptor(f.fileno()) == "image/png");
185 	assert(m.buffer(sample_rar) == "application/x-rar");
186 }