├── .gitignore ├── LICENSE ├── changelog.md ├── puremagic.lua ├── readme.md ├── test_files ├── test-a.webm ├── test.3gp ├── test.7z ├── test.ai ├── test.aiff ├── test.avi ├── test.bmp ├── test.csv ├── test.doc ├── test.docx ├── test.eps ├── test.flac ├── test.flv ├── test.gif ├── test.html ├── test.ico ├── test.jp2 ├── test.jpg ├── test.key.zip ├── test.log ├── test.m4a ├── test.mka ├── test.mkv ├── test.mov ├── test.mp3 ├── test.mp4 ├── test.numbers.zip ├── test.odp ├── test.ods ├── test.odt ├── test.ogg ├── test.ogv ├── test.opus ├── test.pages.zip ├── test.pdf ├── test.php ├── test.pl ├── test.png ├── test.ppt ├── test.pptx ├── test.psd ├── test.py ├── test.rar ├── test.rb ├── test.rss ├── test.rtf ├── test.sh ├── test.svg ├── test.swf ├── test.tab ├── test.tar ├── test.tar.gz ├── test.tiff ├── test.txt ├── test.txt.Z ├── test.txt.bz2 ├── test.txt.gz ├── test.txt.xz ├── test.wav ├── test.webm ├── test.webp ├── test.wma ├── test.wmv ├── test.xhtml ├── test.xls ├── test.xlsx ├── test.xml └── test.zip └── tests.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Will Bond 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # puremagic Changelog 2 | 3 | ## 1.0.1 4 | 5 | - Fixed a bug with non-binary files causing an error with luajit 6 | 7 | ## 1.0.0 8 | 9 | Initial release 10 | -------------------------------------------------------------------------------- /puremagic.lua: -------------------------------------------------------------------------------- 1 | -- puremagic 1.0.1 2 | -- Copyright (c) 2014 Will Bond 3 | -- Licensed under the MIT license. 4 | 5 | 6 | function basename(path) 7 | local basename_match = path:match('[/\\]([^/\\]+)$') 8 | if basename_match then 9 | return basename_match, nil 10 | end 11 | 12 | return path, nil 13 | end 14 | 15 | 16 | function extension(path) 17 | path = path:lower() 18 | local tar_match = path:match('%.(tar%.[^.]+)$') 19 | if tar_match then 20 | return tar_match 21 | end 22 | if path:sub(#path - 11, #path) == '.numbers.zip' then 23 | return 'numbers.zip' 24 | end 25 | if path:sub(#path - 9, #path) == '.pages.zip' then 26 | return 'pages.zip' 27 | end 28 | if path:sub(#path - 7, #path) == '.key.zip' then 29 | return 'key.zip' 30 | end 31 | return path:match('%.([^.]+)$') 32 | end 33 | 34 | 35 | function in_table(value, list) 36 | for i=1, #list do 37 | if list[i] == value then 38 | return true 39 | end 40 | end 41 | return false 42 | end 43 | 44 | 45 | function string_to_bit_table(chars) 46 | local output = {} 47 | for char in chars:gmatch('.') do 48 | local num = string.byte(char) 49 | local bits = {0, 0, 0, 0, 0, 0, 0, 0} 50 | for bit=8, 1, -1 do 51 | if num > 0 then 52 | bits[bit] = math.fmod(num, 2) 53 | num = (num - bits[bit]) / 2 54 | end 55 | end 56 | table.insert(output, bits) 57 | end 58 | return output 59 | end 60 | 61 | 62 | function bit_table_to_string(bits) 63 | local output = {} 64 | for i = 1, #bits do 65 | local num = tonumber(table.concat(bits[i]), 2) 66 | table.insert(output, string.format('%c', num)) 67 | end 68 | return table.concat(output) 69 | end 70 | 71 | 72 | function bitwise_and(a, b) 73 | local a_bytes = string_to_bit_table(a) 74 | local b_bytes = string_to_bit_table(b) 75 | 76 | local output = {} 77 | for i = 1, #a_bytes do 78 | local bits = {0, 0, 0, 0, 0, 0, 0, 0} 79 | for j = 1, 8 do 80 | if a_bytes[i][j] == 1 and b_bytes[i][j] == 1 then 81 | bits[j] = 1 82 | else 83 | bits[j] = 0 84 | end 85 | end 86 | table.insert(output, bits) 87 | end 88 | 89 | return bit_table_to_string(output) 90 | end 91 | 92 | 93 | -- Unpack a little endian byte string into an integer 94 | function unpack_le(chars) 95 | local bit_table = string_to_bit_table(chars) 96 | -- Merge the bits into a string of 1s and 0s 97 | local result = {} 98 | for i=1, #bit_table do 99 | result[#chars + 1 - i] = table.concat(bit_table[i]) 100 | end 101 | return tonumber(table.concat(result), 2) 102 | end 103 | 104 | 105 | -- Unpack a big endian byte string into an integer 106 | function unpack_be(chars) 107 | local bit_table = string_to_bit_table(chars) 108 | -- Merge the bits into a string of 1s and 0s 109 | for i=1, #bit_table do 110 | bit_table[i] = table.concat(bit_table[i]) 111 | end 112 | return tonumber(table.concat(bit_table), 2) 113 | end 114 | 115 | 116 | -- Takes the first 4-8k of an EBML file and identifies if it is matroska or webm 117 | -- and it it contains just video or just audio. 118 | function ebml_parse(content) 119 | local position = 1 120 | local length = #content 121 | 122 | local header_token, header_value, used_bytes = ebml_parse_section(content) 123 | position = position + used_bytes 124 | 125 | 126 | if header_token ~= '\x1AE\xDF\xA3' then 127 | return nil, 'Unable to find EBML ID' 128 | end 129 | 130 | -- The matroska spec sets the default doctype to be 'matroska', however 131 | -- many file specify this anyway. The other option is 'webm'. 132 | local doctype = 'matroska' 133 | if header_value['B\x82'] then 134 | doctype = header_value['B\x82'] 135 | end 136 | 137 | if doctype ~= 'matroska' and doctype ~= 'webm' then 138 | return nil, 'Unknown EBML doctype' 139 | end 140 | 141 | local segment_position = nil 142 | local track_position = nil 143 | local has_video = false 144 | local found_tracks = false 145 | 146 | while position <= length do 147 | local ebml_id, ebml_value, used_bytes = ebml_parse_section(content:sub(position, length)) 148 | position = position + used_bytes 149 | 150 | -- Segment 151 | if ebml_id == '\x18S\x80g' then 152 | segment_position = position 153 | end 154 | 155 | -- Meta seek information 156 | if ebml_id == '\x11M\x9Bt' then 157 | -- Look for the seek info about the tracks token 158 | for i, child in ipairs(ebml_value['M\xBB']) do 159 | if child['S\xAB'] == '\x16T\xAEk' then 160 | track_position = segment_position + unpack_be(child['S\xAC']) 161 | position = track_position 162 | break 163 | end 164 | end 165 | end 166 | 167 | -- Track 168 | if ebml_id == '\x16T\xAEk' then 169 | found_tracks = true 170 | -- Scan through each track looking for video 171 | for i, child in ipairs(ebml_value['\xAE']) do 172 | -- Look to see if the track type is video 173 | if unpack_be(child['\x83']) == 1 then 174 | has_video = true 175 | break 176 | end 177 | end 178 | break 179 | end 180 | end 181 | 182 | if found_tracks and not has_video then 183 | if doctype == 'matroska' then 184 | return 'audio/x-matroska' 185 | else 186 | return 'audio/webm' 187 | end 188 | end 189 | 190 | if doctype == 'matroska' then 191 | return 'video/x-matroska' 192 | else 193 | return 'video/webm' 194 | end 195 | end 196 | 197 | 198 | -- Parses a section of an EBML document, returning the EBML ID at the beginning, 199 | -- plus the value as a table with child EBML IDs as keys and the number of 200 | -- bytes from the content that contained the ID and value 201 | function ebml_parse_section(content) 202 | local ebml_id, element_length, used_bytes = ebml_id_and_length(content) 203 | 204 | -- Don't parse the segment since it is the whole file! 205 | if ebml_id == '\x18\x53\x80\x67' then 206 | return ebml_id, nil, used_bytes 207 | end 208 | 209 | local ebml_value = content:sub(used_bytes + 1, used_bytes + element_length) 210 | used_bytes = used_bytes + element_length 211 | 212 | -- We always parse the return value of level 0/1 elements 213 | local recursive_parse = false 214 | if #ebml_id == 4 then 215 | recursive_parse = true 216 | 217 | -- We need Seek information 218 | elseif ebml_id == '\x4D\xBB' then 219 | recursive_parse = true 220 | 221 | -- We want the top-level of TrackEntry to grab the TrackType 222 | elseif ebml_id == '\xAE' then 223 | recursive_parse = true 224 | end 225 | 226 | if recursive_parse then 227 | local buffer = ebml_value 228 | ebml_value = {} 229 | 230 | -- Track which child entries have been converted to an array 231 | local array_children = {} 232 | 233 | while #buffer > 0 do 234 | local child_ebml_id, child_ebml_value, child_used_bytes = ebml_parse_section(buffer) 235 | 236 | if array_children[child_ebml_id] then 237 | table.insert(ebml_value[child_ebml_id], child_ebml_value) 238 | 239 | -- Single values are just stores by themselves 240 | elseif ebml_value[child_ebml_id] == nil then 241 | -- Force seek info and tracks to be arrays even if there is only one 242 | if child_ebml_id == 'M\xBB' or child_ebml_id == '\xAE' then 243 | child_ebml_value = {child_ebml_value} 244 | array_children[child_ebml_id] = true 245 | end 246 | ebml_value[child_ebml_id] = child_ebml_value 247 | 248 | -- If there is already a value for the ID, turn it into a table 249 | else 250 | ebml_value[child_ebml_id] = {ebml_value[child_ebml_id], child_ebml_value} 251 | array_children[child_ebml_id] = true 252 | end 253 | 254 | -- Move past the part we've parsed 255 | buffer = buffer:sub(child_used_bytes + 1, #buffer) 256 | end 257 | end 258 | 259 | return ebml_id, ebml_value, used_bytes 260 | end 261 | 262 | 263 | -- Should accept 12+ bytes, will return the ebml id, the data length and the 264 | -- number of bytes that were used to hold those values. 265 | function ebml_id_and_length(chars) 266 | -- The ID is encoded the same way as the length, however, we don't want 267 | -- to remove the length bits from the ID value or intepret it as an 268 | -- unsigned int since all of the documentation online references the IDs in 269 | -- encoded form. 270 | local _, id_length = ebml_length(chars:sub(1, 4)) 271 | local ebml_id = chars:sub(1, id_length) 272 | 273 | local remaining = chars:sub(id_length + 1, id_length + 8) 274 | local element_length, used_bytes = ebml_length(remaining) 275 | 276 | return ebml_id, element_length, id_length + used_bytes 277 | end 278 | 279 | 280 | -- Should accept 8+ bytes, will return the data length plus the number of bytes 281 | -- that were used to hold the data length. 282 | function ebml_length(chars) 283 | -- We substring chars to ensure we don't build a huge table we don't need 284 | local bit_tables = string_to_bit_table(chars:sub(1, 8)) 285 | 286 | local value_length = 1 287 | for i=1, #bit_tables[1] do 288 | if bit_tables[1][i] == 0 then 289 | value_length = value_length + 1 290 | else 291 | -- Clear the indicator bit so the rest of the byte 292 | bit_tables[1][i] = 0 293 | break 294 | end 295 | end 296 | 297 | local bits = {} 298 | for i=1, value_length do 299 | table.insert(bits, table.concat(bit_tables[i])) 300 | end 301 | 302 | return tonumber(table.concat(bits), 2), value_length 303 | end 304 | 305 | 306 | function binary_tests(content, ext) 307 | local length = #content 308 | local _1_8 = content:sub(1, 8) 309 | local _1_7 = content:sub(1, 7) 310 | local _1_6 = content:sub(1, 6) 311 | local _1_5 = content:sub(1, 5) 312 | local _1_4 = content:sub(1, 4) 313 | local _1_3 = content:sub(1, 3) 314 | local _1_2 = content:sub(1, 2) 315 | local _9_12 = content:sub(9, 12) 316 | 317 | 318 | -- Images 319 | if _1_4 == '\xC5\xD0\xD3\xC6' then 320 | -- With a Windows-format EPS, the file starts right after a 30-byte 321 | -- header, or a 30-byte header followed by two bytes of padding 322 | if content:sub(33, 42) == '%!PS-Adobe' or content:sub(31, 40) == '%!PS-Adobe' then 323 | return 'application/postscript' 324 | end 325 | end 326 | 327 | if _1_8 == '%!PS-Ado' and content:sub(9, 10) == 'be' then 328 | return 'application/postscript' 329 | end 330 | 331 | if _1_4 == 'MM\x00*' or _1_4 == 'II*\x00' then 332 | return 'image/tiff' 333 | end 334 | 335 | if _1_8 == '\x89PNG\r\n\x1A\n' then 336 | return 'image/png' 337 | end 338 | 339 | if _1_6 == 'GIF87a' or _1_6 == 'GIF89a' then 340 | return 'image/gif' 341 | end 342 | 343 | if _1_4 == 'RIFF' and _9_12 == 'WEBP' then 344 | return 'image/webp' 345 | end 346 | 347 | if _1_2 == 'BM' and length > 14 and in_table(content:sub(15, 15), {'\x0C', '(', '@', '\x80'}) then 348 | return 'image/x-ms-bmp' 349 | end 350 | 351 | local normal_jpeg = length > 10 and in_table(content:sub(7, 10), {'JFIF', 'Exif'}) 352 | local photoshop_jpeg = length > 24 and _1_4 == '\xFF\xD8\xFF\xED' and content:sub(21, 24) == '8BIM' 353 | if normal_jpeg or photoshop_jpeg then 354 | return 'image/jpeg' 355 | end 356 | 357 | if _1_4 == '8BPS' then 358 | return 'image/vnd.adobe.photoshop' 359 | end 360 | 361 | if _1_8 == '\x00\x00\x00\x0CjP ' and _9_12 == '\r\n\x87\n' then 362 | return 'image/jp2' 363 | end 364 | 365 | if _1_4 == '\x00\x00\x01\x00' then 366 | return 'application/vnd.microsoft.icon' 367 | end 368 | 369 | 370 | -- Audio/Video 371 | if _1_4 == '\x1AE\xDF\xA3' and length > 1000 then 372 | local mimetype, err = ebml_parse(content) 373 | 374 | if mimetype then 375 | return mimetype 376 | end 377 | end 378 | 379 | if _1_4 == 'MOVI' then 380 | if in_table(content:sub(5, 8), {'moov', 'mdat'}) then 381 | return 'video/quicktime' 382 | end 383 | end 384 | 385 | if length > 8 and content:sub(5, 8) == 'ftyp' then 386 | local lower_9_12 = _9_12:lower() 387 | 388 | if in_table(lower_9_12, {'avc1', 'isom', 'iso2', 'mp41', 'mp42', 'mmp4', 'ndsc', 'ndsh', 'ndsm', 'ndsp', 'ndss', 'ndxc', 'ndxh', 'ndxm', 'ndxp', 'ndxs', 'f4v ', 'f4p ', 'm4v '}) then 389 | return 'video/mp4' 390 | end 391 | 392 | if in_table(lower_9_12, {'msnv', 'ndas', 'f4a ', 'f4b ', 'm4a ', 'm4b ', 'm4p '}) then 393 | return 'audio/mp4' 394 | end 395 | 396 | if in_table(lower_9_12, {'3g2a', '3g2b', '3g2c', 'kddi'}) then 397 | return 'video/3gpp2' 398 | end 399 | 400 | if in_table(lower_9_12, {'3ge6', '3ge7', '3gg6', '3gp1', '3gp2', '3gp3', '3gp4', '3gp5', '3gp6', '3gs7'}) then 401 | return 'video/3gpp' 402 | end 403 | 404 | if lower_9_12 == 'mqt ' or lower_9_12 == 'qt ' then 405 | return 'video/quicktime' 406 | end 407 | 408 | if lower_9_12 == 'jp2 ' then 409 | return 'image/jp2' 410 | end 411 | end 412 | 413 | -- MP3 414 | if bitwise_and(_1_2, '\xFF\xF6') == '\xFF\xF2' then 415 | local byte_3 = content:sub(3, 3) 416 | if bitwise_and(byte_3, '\xF0') ~= '\xF0' and bitwise_and(byte_3, "\x0C") ~= "\x0C" then 417 | return 'audio/mpeg' 418 | end 419 | end 420 | if _1_3 == 'ID3' then 421 | return 'audio/mpeg' 422 | end 423 | 424 | if _1_4 == 'fLaC' then 425 | return 'audio/x-flac' 426 | end 427 | 428 | if _1_8 == '0&\xB2u\x8Ef\xCF\x11' then 429 | -- Without writing a full-on ASF parser, we can just scan for the 430 | -- UTF-16 string "AspectRatio" 431 | if content:find('\x00A\x00s\x00p\x00e\x00c\x00t\x00R\x00a\x00t\x00i\x00o', 1, true) then 432 | return 'video/x-ms-wmv' 433 | end 434 | return 'audio/x-ms-wma' 435 | end 436 | 437 | if _1_4 == 'RIFF' and _9_12 == 'AVI ' then 438 | return 'video/x-msvideo' 439 | end 440 | 441 | if _1_4 == 'RIFF' and _9_12 == 'WAVE' then 442 | return 'audio/x-wav' 443 | end 444 | 445 | if _1_4 == 'FORM' and _9_12 == 'AIFF' then 446 | return 'audio/x-aiff' 447 | end 448 | 449 | if _1_4 == 'OggS' then 450 | local _29_33 = content:sub(29, 33) 451 | if _29_33 == '\x01vorb' then 452 | return 'audio/vorbis' 453 | end 454 | if _29_33 == '\x07FLAC' then 455 | return 'audio/x-flac' 456 | end 457 | if _29_33 == 'OpusH' then 458 | return 'audio/ogg' 459 | end 460 | -- Theora and OGM 461 | if _29_33 == '\x80theo' or _29_33 == 'vide' then 462 | return 'video/ogg' 463 | end 464 | end 465 | 466 | if _1_3 == 'FWS' or _1_3 == 'CWS' then 467 | return 'application/x-shockwave-flash' 468 | end 469 | 470 | if _1_3 == 'FLV' then 471 | return 'video/x-flv' 472 | end 473 | 474 | 475 | if _1_5 == '%PDF-' then 476 | return 'application/pdf' 477 | end 478 | 479 | if _1_5 == '{\\rtf' then 480 | return 'text/rtf' 481 | end 482 | 483 | 484 | -- Office '97-2003 formats 485 | if _1_8 == '\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1' then 486 | if in_table(ext, {'xls', 'csv', 'tab'}) then 487 | return 'application/vnd.ms-excel' 488 | end 489 | if ext == 'ppt' then 490 | return 'application/vnd.ms-powerpoint' 491 | end 492 | -- We default to word since we need something if the extension isn't recognized 493 | return 'application/msword' 494 | end 495 | 496 | if _1_8 == '\x09\x04\x06\x00\x00\x00\x10\x00' then 497 | return 'application/vnd.ms-excel' 498 | end 499 | 500 | if _1_6 == '\xDB\xA5\x2D\x00\x00\x00' or _1_5 == '\x50\x4F\x5E\x51\x60' or _1_4 == '\xFE\x37\x00\x23' or _1_3 == '\x94\xA6\x2E' then 501 | return 'application/msword' 502 | end 503 | 504 | if _1_4 == 'PK\x03\x04' then 505 | -- Office XML formats 506 | if ext == 'xlsx' then 507 | return 'application/vnd.ms-excel' 508 | end 509 | 510 | if ext == 'pptx' then 511 | return 'application/vnd.ms-powerpoint' 512 | end 513 | 514 | if ext == 'docx' then 515 | return 'application/msword' 516 | end 517 | 518 | -- Open Office formats 519 | if ext == 'ods' then 520 | return 'application/vnd.oasis.opendocument.spreadsheet' 521 | end 522 | 523 | if ext == 'odp' then 524 | return 'application/vnd.oasis.opendocument.presentation' 525 | end 526 | 527 | if ext == 'odt' then 528 | return 'application/vnd.oasis.opendocument.text' 529 | end 530 | 531 | -- iWork - some programs like Mac Mail change the filename to 532 | -- .numbers.zip, etc 533 | if ext == 'pages' or ext == 'pages.zip' then 534 | return 'application/vnd.apple.pages' 535 | end 536 | if ext == 'key' or ext == 'key.zip' then 537 | return 'application/vnd.apple.keynote' 538 | end 539 | if ext == 'numbers' or ext == 'numbers.zip' then 540 | return 'application/vnd.apple.numbers' 541 | end 542 | 543 | -- Otherwise just a zip 544 | return 'application/zip' 545 | end 546 | 547 | 548 | -- Archives 549 | if length > 257 then 550 | if content:sub(258, 263) == 'ustar\x00' then 551 | return 'application/x-tar' 552 | end 553 | if content:sub(258, 265) == 'ustar\x40\x40\x00' then 554 | return 'application/x-tar' 555 | end 556 | end 557 | 558 | if _1_7 == 'Rar!\x1A\x07\x00' or _1_8 == 'Rar!\x1A\x07\x01\x00' then 559 | return 'application/x-rar-compressed' 560 | end 561 | 562 | if _1_2 == '\x1F\x9D' then 563 | return 'application/x-compress' 564 | end 565 | 566 | if _1_2 == '\x1F\x8B' then 567 | return 'application/x-gzip' 568 | end 569 | 570 | if _1_3 == 'BZh' then 571 | return 'application/x-bzip2' 572 | end 573 | 574 | if _1_6 == '\xFD7zXZ\x00' then 575 | return 'application/x-xz' 576 | end 577 | 578 | if _1_6 == '7z\xBC\xAF\x27\x1C' then 579 | return 'application/x-7z-compressed' 580 | end 581 | 582 | if _1_2 == 'MZ' then 583 | local pe_header_start = unpack_le(content:sub(61, 64)) 584 | local signature = content:sub(pe_header_start + 1, pe_header_start + 4) 585 | 586 | if signature == 'PE\x00\x00' then 587 | local image_file_header_start = pe_header_start + 5 588 | local characteristics = content:sub(image_file_header_start + 18, image_file_header_start + 19) 589 | local is_dll = bitwise_and(characteristics, '\x20\x00') == '\x20\x00' 590 | 591 | if is_dll then 592 | return 'application/x-msdownload' 593 | end 594 | 595 | return 'application/octet-stream' 596 | end 597 | end 598 | 599 | return nil 600 | end 601 | 602 | 603 | function text_tests(content) 604 | local lower_content = content:lower() 605 | 606 | if content:find('^%%!PS-Adobe') then 607 | return 'application/postscript' 608 | end 609 | 610 | if lower_content:find(' 2 | 3 | 4 | Test 5 | 6 | 7 | This is a test 8 | 9 | 10 | -------------------------------------------------------------------------------- /test_files/test.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.ico -------------------------------------------------------------------------------- /test_files/test.jp2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.jp2 -------------------------------------------------------------------------------- /test_files/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.jpg -------------------------------------------------------------------------------- /test_files/test.key.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.key.zip -------------------------------------------------------------------------------- /test_files/test.log: -------------------------------------------------------------------------------- 1 | A 2 | B 3 | C 4 | D 5 | E 6 | F 7 | G 8 | H 9 | I 10 | J 11 | K 12 | L 13 | M 14 | N 15 | O 16 | P 17 | Q 18 | R 19 | S 20 | T 21 | U 22 | V 23 | W 24 | X 25 | Y 26 | Z 27 | -------------------------------------------------------------------------------- /test_files/test.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.m4a -------------------------------------------------------------------------------- /test_files/test.mka: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.mka -------------------------------------------------------------------------------- /test_files/test.mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.mkv -------------------------------------------------------------------------------- /test_files/test.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.mov -------------------------------------------------------------------------------- /test_files/test.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.mp3 -------------------------------------------------------------------------------- /test_files/test.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.mp4 -------------------------------------------------------------------------------- /test_files/test.numbers.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.numbers.zip -------------------------------------------------------------------------------- /test_files/test.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.odp -------------------------------------------------------------------------------- /test_files/test.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.ods -------------------------------------------------------------------------------- /test_files/test.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.odt -------------------------------------------------------------------------------- /test_files/test.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.ogg -------------------------------------------------------------------------------- /test_files/test.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.ogv -------------------------------------------------------------------------------- /test_files/test.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.opus -------------------------------------------------------------------------------- /test_files/test.pages.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.pages.zip -------------------------------------------------------------------------------- /test_files/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.pdf -------------------------------------------------------------------------------- /test_files/test.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test 5 | This is a test 6 | http://example.com 7 | Mon, 06 Sep 2010 00:01:00 +0000 8 | Sun, 06 Sep 2009 16:20:00 +0000 9 | 1800 10 | 11 | 12 | Test 13 | This is a test 14 | http://example.com/test 15 | 7bd204c6-1655-4c27-aeee-53f933c5395f 16 | Sun, 06 Sep 2009 16:20:00 +0000 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test_files/test.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf210 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;} 3 | {\colortbl;\red255\green255\blue255;} 4 | \margl1440\margr1440\vieww10800\viewh8400\viewkind0 5 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural 6 | 7 | \f0\fs24 \cf0 This is a test} -------------------------------------------------------------------------------- /test_files/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo 'This is a test' 4 | -------------------------------------------------------------------------------- /test_files/test.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test_files/test.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.swf -------------------------------------------------------------------------------- /test_files/test.tab: -------------------------------------------------------------------------------- 1 | 1 2 3 2 | -------------------------------------------------------------------------------- /test_files/test.tar: -------------------------------------------------------------------------------- 1 | test.txt000644 000765 000024 00000000017 12413036623 013050 0ustar00wbondstaff000000 000000 This is a test 2 | -------------------------------------------------------------------------------- /test_files/test.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.tar.gz -------------------------------------------------------------------------------- /test_files/test.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.tiff -------------------------------------------------------------------------------- /test_files/test.txt: -------------------------------------------------------------------------------- 1 | This is a test 2 | -------------------------------------------------------------------------------- /test_files/test.txt.Z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.txt.Z -------------------------------------------------------------------------------- /test_files/test.txt.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.txt.bz2 -------------------------------------------------------------------------------- /test_files/test.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.txt.gz -------------------------------------------------------------------------------- /test_files/test.txt.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.txt.xz -------------------------------------------------------------------------------- /test_files/test.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.wav -------------------------------------------------------------------------------- /test_files/test.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.webm -------------------------------------------------------------------------------- /test_files/test.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.webp -------------------------------------------------------------------------------- /test_files/test.wma: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.wma -------------------------------------------------------------------------------- /test_files/test.wmv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.wmv -------------------------------------------------------------------------------- /test_files/test.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test 6 | 7 | 8 | This is a test 9 | 10 | 11 | -------------------------------------------------------------------------------- /test_files/test.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.xls -------------------------------------------------------------------------------- /test_files/test.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.xlsx -------------------------------------------------------------------------------- /test_files/test.xml: -------------------------------------------------------------------------------- 1 | 2 | This is a test 3 | -------------------------------------------------------------------------------- /test_files/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbond/puremagic/28e57abef8f10daa2fe50b64e3ee6df270777d42/test_files/test.zip -------------------------------------------------------------------------------- /tests.lua: -------------------------------------------------------------------------------- 1 | local puremagic = require('puremagic') 2 | 3 | 4 | local successful = 0 5 | local run = 0 6 | 7 | 8 | function test_file(name, mimetype) 9 | local path = './test_files/' .. name 10 | local inspected_mimetype = puremagic.via_path(path) 11 | if inspected_mimetype ~= mimetype then 12 | print('\x1B[31mERROR\x1B[0m ' .. path .. ' detected as ' .. inspected_mimetype .. ' instead of ' .. mimetype) 13 | else 14 | print('\x1B[32mOK\x1B[0m ' .. path .. ' detected as ' .. inspected_mimetype) 15 | successful = successful + 1 16 | end 17 | run = run + 1 18 | end 19 | 20 | 21 | test_file('test.3gp', 'video/3gpp') 22 | test_file('test.7z', 'application/x-7z-compressed') 23 | test_file('test.ai', 'application/pdf') 24 | test_file('test.aiff', 'audio/x-aiff') 25 | test_file('test.avi', 'video/x-msvideo') 26 | test_file('test.bmp', 'image/x-ms-bmp') 27 | test_file('test.csv', 'text/csv') 28 | test_file('test.doc', 'application/msword') 29 | test_file('test.docx', 'application/msword') 30 | test_file('test.eps', 'application/postscript') 31 | test_file('test.flac', 'audio/x-flac') 32 | test_file('test.flv', 'video/x-flv') 33 | test_file('test.gif', 'image/gif') 34 | test_file('test.html', 'text/html') 35 | test_file('test.ico', 'application/vnd.microsoft.icon') 36 | test_file('test.jp2', 'image/jp2') 37 | test_file('test.jpg', 'image/jpeg') 38 | test_file('test.key.zip', 'application/vnd.apple.keynote') 39 | test_file('test.log', 'text/plain') 40 | test_file('test.m4a', 'audio/mp4') 41 | test_file('test.mka', 'audio/x-matroska') 42 | test_file('test.mkv', 'video/x-matroska') 43 | test_file('test.mov', 'video/quicktime') 44 | test_file('test.mp3', 'audio/mpeg') 45 | test_file('test.mp4', 'video/mp4') 46 | test_file('test.numbers.zip', 'application/vnd.apple.numbers') 47 | test_file('test.odp', 'application/vnd.oasis.opendocument.presentation') 48 | test_file('test.ods', 'application/vnd.oasis.opendocument.spreadsheet') 49 | test_file('test.odt', 'application/vnd.oasis.opendocument.text') 50 | test_file('test.ogg', 'audio/vorbis') 51 | test_file('test.ogv', 'video/ogg') 52 | test_file('test.opus', 'audio/ogg') 53 | test_file('test.pages.zip', 'application/vnd.apple.pages') 54 | test_file('test.pdf', 'application/pdf') 55 | test_file('test.php', 'application/x-httpd-php') 56 | test_file('test.pl', 'application/x-perl') 57 | test_file('test.png', 'image/png') 58 | test_file('test.ppt', 'application/vnd.ms-powerpoint') 59 | test_file('test.pptx', 'application/vnd.ms-powerpoint') 60 | test_file('test.psd', 'image/vnd.adobe.photoshop') 61 | test_file('test.py', 'application/x-python') 62 | test_file('test.rar', 'application/x-rar-compressed') 63 | test_file('test.rb', 'application/x-ruby') 64 | test_file('test.rss', 'application/rss+xml') 65 | test_file('test.rtf', 'text/rtf') 66 | test_file('test.sh', 'text/x-shellscript') 67 | test_file('test.svg', 'image/svg+xml') 68 | test_file('test.swf', 'application/x-shockwave-flash') 69 | test_file('test.tab', 'text/tab-separated-values') 70 | test_file('test.tar', 'application/x-tar') 71 | test_file('test.tar.gz', 'application/x-gzip') 72 | test_file('test.tiff', 'image/tiff') 73 | test_file('test.txt', 'text/plain') 74 | test_file('test.txt.bz2', 'application/x-bzip2') 75 | test_file('test.txt.gz', 'application/x-gzip') 76 | test_file('test.txt.xz', 'application/x-xz') 77 | test_file('test.txt.Z', 'application/x-compress') 78 | test_file('test.wav', 'audio/x-wav') 79 | test_file('test.webm', 'video/webm') 80 | test_file('test-a.webm', 'audio/webm') 81 | test_file('test.webp', 'image/webp') 82 | test_file('test.wma', 'audio/x-ms-wma') 83 | test_file('test.wmv', 'video/x-ms-wmv') 84 | test_file('test.xhtml', 'application/xhtml+xml') 85 | test_file('test.xls', 'application/vnd.ms-excel') 86 | test_file('test.xlsx', 'application/vnd.ms-excel') 87 | test_file('test.xml', 'application/xml') 88 | test_file('test.zip', 'application/zip') 89 | 90 | print() 91 | print(successful .. ' of ' .. run .. ' successful') 92 | if successful ~= run then 93 | print((run - successful) .. ' errors') 94 | end 95 | --------------------------------------------------------------------------------