Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
h2non committed Sep 24, 2015
0 parents commit 4af2d19
Show file tree
Hide file tree
Showing 18 changed files with 767 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
indent_style = tabs
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/bimg
/bundle
bin
/*.jpg
/*.png
/*.webp
/fixtures/*_out.*
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: go
go:
- 1.5
- 1.4
- 1.3
- release
- tip
24 changes: 24 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
The MIT License

Copyright (c) Tomas Aparicio

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# filetype [![Build Status](https://travis-ci.org/h2non/filetype.png)](https://travis-ci.org/h2non/filetype) [![GoDoc](https://godoc.org/github.com/h2non/filetype?status.svg)](https://godoc.org/github.com/h2non/filetype)

Small [Go](https://golang.org) package to infer the file type checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of a given binary buffer.

Supports a wide range of file types, including images formats, fonts, videos, audio and other common application files, and provides the proper file extension and convenient MIME code.

## Installation

```bash
go get gopkg.in/h2non/filetype.v0
```

## Usage

```go
import (
"fmt"
"io/ioutil"
"gopkg.in/h2non/filetype.v0"
)

func main() {
buf, _ := ioutil.ReadFile("sample.jpg")

kind, unkwown := filetype.Type(buf)
if unkwown != nil {
fmt.Printf("Unkwown file type")
return
}

fmt.Printf("File type found: %s. MIME: %s", kind.Extension, kind.MIME.Value)
}
```

## API



## License

MIT - Tomas Aparicio
116 changes: 116 additions & 0 deletions filetype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package filetype

import (
"errors"
"gopkg.in/h2non/filetype.v0/matchers"
"gopkg.in/h2non/filetype.v0/types"
)

// Map of extensions and file types
var Types = types.Types

// Map of file matchers
var Matchers = matchers.Matchers

// Default types
var Empty = types.Empty
var Unknown = types.Unknown

// Predefined errors
var EmptyBufferErr = errors.New("Empty buffer")
var UnknownBufferErr = errors.New("Unknown buffer type")

func DoMatch(buf []byte) (types.Type, error) {
return matchers.Match(buf)
}

// Infer the file type of a buffer inspecting the magic numbers
func Match(buf []byte) (types.Type, error) {
return DoMatch(buf)
}

// Alias to Match()
func Type(buf []byte) (types.Type, error) {
return Match(buf)
}

func doMatchMap(buf []byte, machers matchers.Map) (types.Type, error) {
kind := matchers.MatchMap(buf, machers)
if kind != types.Unknown {
return kind, nil
}
return kind, UnknownBufferErr
}

// Match file as image type
func Image(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Image)
}

// Match file as audio type
func Audio(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Audio)
}

// Match file as video type
func Video(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Audio)
}

// Match file as text font type
func Font(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Font)
}

// Match file as generic archive type
func Archive(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Archive)
}

func Is(buf []byte, ext string) bool {
kind, ok := types.Types[ext]
if ok {
return IsType(buf, kind)
}
return false
}

func IsType(buf []byte, kind types.Type) bool {
matcher := matchers.Matchers[kind]
if matcher == nil {
return false
}

length := len(buf)
return matcher(buf, length) != types.Unknown
}

// Register a new matcher type
func AddMatcher(fileType types.Type, matcher matchers.Matcher) matchers.TypeMatcher {
return matchers.NewMatcher(fileType, matcher)
}

// Register a new file type
func AddType(ext, mime string) types.Type {
return types.NewType(ext, mime)
}

// Check if a given file extension is supported
func IsSupported(ext string) bool {
for name, _ := range Types {
if name == ext {
return true
}
}
return false
}

// Check if a given MIME expression is supported
func IsMIMESupported(mime string) bool {
for _, m := range Types {
if m.MIME.Value == mime {
return true
}
}
return false
}
25 changes: 25 additions & 0 deletions filetype_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package filetype

import (
"testing"
)

func TestMatches(t *testing.T) {
cases := []struct {
buf []byte
ext string
}{
{[]byte{0xFF, 0xD8, 0xFF}, "jpg"},
}

for _, test := range cases {
match, err := Match(test.buf)
if err != nil {
t.Fatalf("Error: %s", err)
}

if match.Extension != test.ext {
t.Fatalf("Invalid image type: %s", match.Extension)
}
}
}
134 changes: 134 additions & 0 deletions matchers/archive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package matchers

var TypeEpub = NewType("epub", "application/epub+zip")
var TypeZip = NewType("zip", "application/zip")
var TypeTar = NewType("tar", "application/x-tar")
var TypeRar = NewType("rar", "application/x-rar-compressed")
var TypeGz = NewType("gz", "application/gzip")
var TypeBz2 = NewType("bz2", "application/x-bzip2")
var Type7z = NewType("7z", "application/x-7z-compressed")
var TypeXz = NewType("xz", "application/x-xz")
var TypePdf = NewType("pdf", "application/pdf")
var TypeExe = NewType("exe", "application/x-msdownload")
var TypeSwf = NewType("swf", "application/x-shockwave-flash")
var TypeRtf = NewType("rtf", "application/rtf")
var TypeEot = NewType("eot", "application/octet-stream")
var TypePs = NewType("ps", "application/postscript")
var TypeSqlite = NewType("sqlite", "application/x-sqlite3")

var Archive = Map{
TypeEpub: Epub,
TypeZip: Zip,
TypeTar: Tar,
TypeRar: Rar,
TypeBz2: Bz2,
Type7z: SevenZ,
TypeXz: Xz,
TypePdf: Pdf,
TypeExe: Exe,
TypeSwf: Swf,
TypeRtf: Rtf,
TypeEot: Eot,
TypePs: Ps,
TypeSqlite: Sqlite,
}

func Epub(buf []byte, length int) bool {
return length > 57 &&
buf[0] == 0x50 && buf[1] == 0x4B && buf[2] == 0x3 && buf[3] == 0x4 &&
buf[30] == 0x6D && buf[31] == 0x69 && buf[32] == 0x6D && buf[33] == 0x65 &&
buf[34] == 0x74 && buf[35] == 0x79 && buf[36] == 0x70 && buf[37] == 0x65 &&
buf[38] == 0x61 && buf[39] == 0x70 && buf[40] == 0x70 && buf[41] == 0x6C &&
buf[42] == 0x69 && buf[43] == 0x63 && buf[44] == 0x61 && buf[45] == 0x74 &&
buf[46] == 0x69 && buf[47] == 0x6F && buf[48] == 0x6E && buf[49] == 0x2F &&
buf[50] == 0x65 && buf[51] == 0x70 && buf[52] == 0x75 && buf[53] == 0x62 &&
buf[54] == 0x2B && buf[55] == 0x7A && buf[56] == 0x69 && buf[57] == 0x70
}

func Zip(buf []byte, length int) bool {
return length > 3 &&
buf[0] == 0x50 && buf[1] == 0x4B &&
(buf[2] == 0x3 || buf[2] == 0x5 || buf[2] == 0x7) &&
(buf[3] == 0x4 || buf[3] == 0x6 || buf[3] == 0x8)
}

func Tar(buf []byte, length int) bool {
return length > 261 &&
buf[257] == 0x75 && buf[258] == 0x73 &&
buf[259] == 0x74 && buf[260] == 0x61 &&
buf[261] == 0x72
}

func Rar(buf []byte, length int) bool {
return length > 6 &&
buf[0] == 0x52 && buf[1] == 0x61 && buf[2] == 0x72 &&
buf[3] == 0x21 && buf[4] == 0x1A && buf[5] == 0x7 &&
(buf[6] == 0x0 || buf[6] == 0x1)
}

func Gz(buf []byte, length int) bool {
return length > 2 &&
buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x8
}

func Bz2(buf []byte, length int) bool {
return length > 2 &&
buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68
}

func SevenZ(buf []byte, length int) bool {
return length > 5 &&
buf[0] == 0x37 && buf[1] == 0x7A && buf[2] == 0xBC &&
buf[3] == 0xAF && buf[4] == 0x27 && buf[5] == 0x1C
}

func Pdf(buf []byte, length int) bool {
return length > 3 &&
buf[0] == 0x25 && buf[1] == 0x50 &&
buf[2] == 0x44 && buf[3] == 0x46
}

func Exe(buf []byte, length int) bool {
return length > 1 &&
buf[0] == 0x4D && buf[1] == 0x5A
}

func Swf(buf []byte, length int) bool {
return length > 2 &&
(buf[0] == 0x43 || buf[0] == 0x46) &&
buf[1] == 0x57 && buf[2] == 0x53
}

func Rtf(buf []byte, length int) bool {
return length > 4 &&
buf[0] == 0x7B && buf[1] == 0x5C &&
buf[2] == 0x72 && buf[3] == 0x74 &&
buf[4] == 0x66
}

func Eot(buf []byte, length int) bool {
return length > 10 &&
buf[34] == 0x4C && buf[35] == 0x50 &&
((buf[8] == 0x02 && buf[9] == 0x00 &&
buf[10] == 0x01) || (buf[8] == 0x01 &&
buf[9] == 0x00 && buf[10] == 0x00) ||
(buf[8] == 0x02 && buf[9] == 0x00 && buf[10] == 0x02))
}

func Ps(buf []byte, length int) bool {
return length > 1 &&
buf[0] == 0x25 && buf[1] == 0x21
}

func Xz(buf []byte, length int) bool {
return length > 5 &&
buf[0] == 0xFD && buf[1] == 0x37 &&
buf[2] == 0x7A && buf[3] == 0x58 &&
buf[4] == 0x5A && buf[5] == 0x00
}

func Sqlite(buf []byte, length int) bool {
return length > 3 &&
buf[0] == 0x53 && buf[1] == 0x51 &&
buf[2] == 0x4C && buf[3] == 0x69
}
Loading

0 comments on commit 4af2d19

Please sign in to comment.