feat: release

This commit is contained in:
Tomas Aparicio
2015-09-26 18:37:07 +01:00
parent e8ab6370a6
commit d78a065316
14 changed files with 502 additions and 42 deletions

View File

@@ -1,7 +1,7 @@
language: go
script: go test ./...
go:
- 1.5
- 1.4
- 1.3
- release
- tip

185
README.md
View File

@@ -1,8 +1,16 @@
# 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.
Small [Go](https://golang.org) package to infer the file and MIME type checking the [magic numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) signature.
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.
## Features
- Supports a [wide range](#supported-types) of file types
- Provides file extension and proper MIME type
- File discovery by extension or MIME type
- File discovery by class (image, video, audio...)
- Bunch of helpers and shortcuts for easy file checking
- Pluggable by default: plug in new types and file matchers
- Simple and semantic API
## Installation
@@ -10,31 +18,192 @@ Supports a wide range of file types, including images formats, fonts, videos, au
go get gopkg.in/h2non/filetype.v0
```
## Usage
## Usage
```go
import "gopkg.in/h2non/filetype.v0"
```
## API
See [Godoc](https://godoc.org/github.com/h2non/filetype) reference.
## Examples
#### Simple file type checking
```go
package main
import (
"fmt"
"io/ioutil"
"gopkg.in/h2non/filetype.v0"
"io/ioutil"
)
func main() {
buf, _ := ioutil.ReadFile("sample.jpg")
kind, unkwown := filetype.Type(buf)
kind, unkwown := filetype.Match(buf)
if unkwown != nil {
fmt.Printf("Unkwown file type")
fmt.Printf("Unkwown: %s", unkwown)
return
}
fmt.Printf("File type found: %s. MIME: %s", kind.Extension, kind.MIME.Value)
fmt.Printf("File type: %s. MIME: %s\n", kind.Extension, kind.MIME.Value)
}
```
## API
#### Check type class
```go
package main
import (
"fmt"
"gopkg.in/h2non/filetype.v0"
"io/ioutil"
)
func main() {
buf, _ := ioutil.ReadFile("sample.jpg")
if filetype.IsImage(buf) {
fmt.Println("Image file")
} else {
fmt.Println("Not an image")
}
}
```
#### Supported type
```go
package main
import (
"fmt"
"gopkg.in/h2non/filetype.v0"
)
func main() {
// Check if file is supported by extension
if filetype.IsSupported("jpg") {
fmt.Println("Extension supported")
} else {
fmt.Println("Extension not supported")
}
// Check if file is supported by extension
if filetype.IsMIMESupported("image/jpeg") {
fmt.Println("MIME type supported")
} else {
fmt.Println("MIME type not supported")
}
}
```
#### Add additional file type matchers
```go
package main
import (
"fmt"
"gopkg.in/h2non/filetype.v0"
)
var fooType = filetype.NewType("foo", "foo/foo")
func fooMatcher(buf []byte, length int) bool {
return length > 1 && buf[0] == 0x01 && buf[1] == 0x02
}
func main() {
// Register the new matcher and its type
filetype.AddMatcher(fooType, fooMatcher)
// Check if the new type is supported by extension
if filetype.IsSupported("foo") {
fmt.Println("Suppored type: foo")
}
// Check if the new type is supported by MIME
if filetype.IsMIMESupported("foo/foo") {
fmt.Println("Suppored type: foo/foo")
}
// Try to match the file
fooFile := []byte{0x01, 0x02}
kind, _ := filetype.Match(fooFile)
if kind == filetype.Unknown {
fmt.Println("Unknown file type")
} else {
fmt.Printf("File type matched: %s\n", kind.Extension)
}
}
```
## Supported types
#### Image
- **jpg** - `image/jpeg`
- **png** - `image/png`
- **gif** - `image/gif`
- **webp** - `image/webp`
- **cr2** - `image/x-canon-cr2`
- **tif** - `image/tiff`
- **bmp** - `image/bmp`
- **jxr** - `image/vnd.ms-photo`
- **psd** - `image/vnd.adobe.photoshop`
- **ico** - `image/x-icon`
#### Video
- **mp4** - `video/mp4`
- **m4v** - `video/x-m4v`
- **mkv** - `video/x-matroska`
- **webm** - `video/webm`
- **mov** - `video/quicktime`
- **avi** - `video/x-msvideo`
- **wmv** - `video/x-ms-wmv`
- **mpg** - `video/mpeg`
- **flv** - `video/x-flv`
#### Audio
- **mid** - `audio/midi`
- **mp3** - `audio/mpeg`
- **m4a** - `audio/m4a`
- **ogg** - `audio/ogg`
- **flac** - `audio/x-flac`
- **wav** - `audio/x-wav`
#### Archive
- **epub** - `application/epub+zip`
- **zip** - `application/zip`
- **tar** - `application/x-tar`
- **rar** - `application/x-rar-compressed`
- **gz** - `application/gzip`
- **bz2** - `application/x-bzip2`
- **7z** - `application/x-7z-compressed`
- **xz** - `application/x-xz`
- **pdf** - `application/pdf`
- **exe** - `application/x-msdownload`
- **swf** - `application/x-shockwave-flash`
- **rtf** - `application/rtf`
- **eot** - `application/octet-stream`
- **ps** - `application/postscript`
- **sqlite** - `application/x-sqlite3`
#### Font
- **woff** - `application/font-woff`
- **woff2** - `application/font-woff`
- **ttf** - `application/font-sfnt`
- **otf** - `application/font-sfnt`
## License

View File

@@ -12,14 +12,19 @@ var Types = types.Types
// Create and register a new type
var NewType = types.NewType
// Default types
var Empty = types.Empty
// Default unknown file type
var Unknown = types.Unknown
// Predefined errors
var EmptyBufferErr = errors.New("Empty buffer")
var UnknownBufferErr = errors.New("Unknown buffer type")
// Register a new file type
func AddType(ext, mime string) types.Type {
return types.NewType(ext, mime)
}
// Checks if a given buffer matches with the given file type extension
func Is(buf []byte, ext string) bool {
kind, ok := types.Types[ext]
if ok {
@@ -28,24 +33,29 @@ func Is(buf []byte, ext string) bool {
return false
}
// Semantic alias to Is()
func IsExtension(buf []byte, ext string) bool {
return Is(buf, ext)
}
// Checks if a given buffer matches with the given file type
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
return matcher(buf, len(buf)) != 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)
// Checks if a given buffer matches with the given MIME type
func IsMIME(buf []byte, mime string) bool {
for _, kind := range types.Types {
if kind.MIME.Value == mime {
matcher := matchers.Matchers[kind]
return matcher(buf, len(buf)) != types.Unknown
}
}
return false
}
// Check if a given file extension is supported
@@ -58,7 +68,7 @@ func IsSupported(ext string) bool {
return false
}
// Check if a given MIME expression is supported
// Check if a given MIME type is supported
func IsMIMESupported(mime string) bool {
for _, m := range Types {
if m.MIME.Value == mime {
@@ -67,3 +77,8 @@ func IsMIMESupported(mime string) bool {
}
return false
}
// Retrieve a Type by file extension
func GetType(ext string) types.Type {
return types.Get(ext)
}

View File

@@ -1,25 +1,122 @@
package filetype
import (
"gopkg.in/h2non/filetype.v0/types"
"testing"
)
func TestMatches(t *testing.T) {
func TestIs(t *testing.T) {
cases := []struct {
buf []byte
ext string
buf []byte
ext string
match bool
}{
{[]byte{0xFF, 0xD8, 0xFF}, "jpg"},
{[]byte{0xFF, 0xD8, 0xFF}, "jpg", true},
{[]byte{0xFF, 0xD8, 0x00}, "jpg", false},
{[]byte{0x89, 0x50, 0x4E, 0x47}, "png", true},
}
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)
if Is(test.buf, test.ext) != test.match {
t.Fatalf("Invalid match: %s", test.ext)
}
}
}
func TestIsType(t *testing.T) {
cases := []struct {
buf []byte
kind types.Type
match bool
}{
{[]byte{0xFF, 0xD8, 0xFF}, types.Get("jpg"), true},
{[]byte{0xFF, 0xD8, 0x00}, types.Get("jpg"), false},
{[]byte{0x89, 0x50, 0x4E, 0x47}, types.Get("png"), true},
}
for _, test := range cases {
if IsType(test.buf, test.kind) != test.match {
t.Fatalf("Invalid match: %s", test.kind.Extension)
}
}
}
func TestIsMIME(t *testing.T) {
cases := []struct {
buf []byte
mime string
match bool
}{
{[]byte{0xFF, 0xD8, 0xFF}, "image/jpeg", true},
{[]byte{0xFF, 0xD8, 0x00}, "image/jpeg", false},
{[]byte{0x89, 0x50, 0x4E, 0x47}, "image/png", true},
}
for _, test := range cases {
if IsMIME(test.buf, test.mime) != test.match {
t.Fatalf("Invalid match: %s", test.mime)
}
}
}
func TestIsSupported(t *testing.T) {
cases := []struct {
ext string
match bool
}{
{"jpg", true},
{"jpeg", false},
{"abc", false},
{"png", true},
{"mp4", true},
{"", false},
}
for _, test := range cases {
if IsSupported(test.ext) != test.match {
t.Fatalf("Invalid match: %s", test.ext)
}
}
}
func TestIsMIMESupported(t *testing.T) {
cases := []struct {
mime string
match bool
}{
{"image/jpeg", true},
{"foo/bar", false},
{"image/png", true},
{"video/mpeg", true},
}
for _, test := range cases {
if IsMIMESupported(test.mime) != test.match {
t.Fatalf("Invalid match: %s", test.mime)
}
}
}
func TestAddType(t *testing.T) {
AddType("foo", "foo/foo")
if !IsSupported("foo") {
t.Fatalf("Not supported extension")
}
if !IsMIMESupported("foo/foo") {
t.Fatalf("Not supported MIME type")
}
}
func TestGetType(t *testing.T) {
jpg := GetType("jpg")
if jpg == types.Unknown {
t.Fatalf("Type should be supported")
}
invalid := GetType("invalid")
if invalid != Unknown {
t.Fatalf("Type should not be supported")
}
}

10
kind.go
View File

@@ -5,7 +5,7 @@ import (
"gopkg.in/h2non/filetype.v0/types"
)
// Match file as image type
// Try to match a file as image type
func Image(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Image)
}
@@ -16,7 +16,7 @@ func IsImage(buf []byte) bool {
return kind != types.Unknown
}
// Match file as audio type
// Try to match a file as audio type
func Audio(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Audio)
}
@@ -27,7 +27,7 @@ func IsAudio(buf []byte) bool {
return kind != types.Unknown
}
// Match file as video type
// Try to match a file as video type
func Video(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Video)
}
@@ -38,7 +38,7 @@ func IsVideo(buf []byte) bool {
return kind != types.Unknown
}
// Match file as text font type
// Try to match a file as text font type
func Font(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Font)
}
@@ -49,7 +49,7 @@ func IsFont(buf []byte) bool {
return kind != types.Unknown
}
// Match file as generic archive type
// Try to match a file as generic archive type
func Archive(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Archive)
}

View File

@@ -1 +1,40 @@
package filetype
import (
"testing"
)
func TestKind(t *testing.T) {
var cases = []struct {
buf []byte
ext string
}{
{[]byte{0xFF, 0xD8, 0xFF}, "jpg"},
{[]byte{0x89, 0x50, 0x4E, 0x47}, "png"},
{[]byte{0x89, 0x0, 0x0}, "unknown"},
}
for _, test := range cases {
kind, _ := Image(test.buf)
if kind.Extension != test.ext {
t.Fatalf("Invalid match: %s != %s", kind.Extension, test.ext)
}
}
}
func TestIsKind(t *testing.T) {
var cases = []struct {
buf []byte
match bool
}{
{[]byte{0xFF, 0xD8, 0xFF}, true},
{[]byte{0x89, 0x50, 0x4E, 0x47}, true},
{[]byte{0x89, 0x0, 0x0}, false},
}
for _, test := range cases {
if IsImage(test.buf) != test.match {
t.Fatalf("Invalid match: %s", test.match)
}
}
}

View File

@@ -15,7 +15,7 @@ var NewMatcher = matchers.NewMatcher
func Match(buf []byte) (types.Type, error) {
length := len(buf)
if length == 0 {
return types.Empty, nil
return types.Unknown, EmptyBufferErr
}
for _, checker := range Matchers {
@@ -33,6 +33,11 @@ func Get(buf []byte) (types.Type, error) {
return Match(buf)
}
// Register a new matcher type
func AddMatcher(fileType types.Type, matcher matchers.Matcher) matchers.TypeMatcher {
return matchers.NewMatcher(fileType, matcher)
}
// Checks if the given buffer matches with some supported file type
func Matches(buf []byte) bool {
kind, _ := Match(buf)

View File

@@ -1 +1,100 @@
package filetype
import (
"gopkg.in/h2non/filetype.v0/matchers"
"gopkg.in/h2non/filetype.v0/types"
"testing"
)
func TestMatch(t *testing.T) {
cases := []struct {
buf []byte
ext string
}{
{[]byte{0xFF, 0xD8, 0xFF}, "jpg"},
{[]byte{0xFF, 0xD8, 0x00}, "unknown"},
{[]byte{0x89, 0x50, 0x4E, 0x47}, "png"},
}
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)
}
}
}
func TestMatches(t *testing.T) {
cases := []struct {
buf []byte
match bool
}{
{[]byte{0xFF, 0xD8, 0xFF}, true},
{[]byte{0xFF, 0x0, 0x0}, false},
{[]byte{0x89, 0x50, 0x4E, 0x47}, true},
}
for _, test := range cases {
if Matches(test.buf) != test.match {
t.Fatalf("Do not matches: %#v", test.buf)
}
}
}
func TestAddMatcher(t *testing.T) {
fileType := AddType("foo", "foo/foo")
AddMatcher(fileType, func(buf []byte, l int) bool {
return l == 2 && buf[0] == 0x00 && buf[1] == 0x00
})
if !Is([]byte{0x00, 0x00}, "foo") {
t.Fatalf("Type cannot match")
}
if !IsSupported("foo") {
t.Fatalf("Not supported extension")
}
if !IsMIMESupported("foo/foo") {
t.Fatalf("Not supported MIME type")
}
}
func TestMatchMap(t *testing.T) {
cases := []struct {
buf []byte
kind types.Type
}{
{[]byte{0xFF, 0xD8, 0xFF}, types.Get("jpg")},
{[]byte{0x89, 0x50, 0x4E, 0x47}, types.Get("png")},
{[]byte{0xFF, 0x0, 0x0}, Unknown},
}
for _, test := range cases {
if kind := MatchMap(test.buf, matchers.Image); kind != test.kind {
t.Fatalf("Do not matches: %#v", test.buf)
}
}
}
func TestMatchesMap(t *testing.T) {
cases := []struct {
buf []byte
match bool
}{
{[]byte{0xFF, 0xD8, 0xFF}, true},
{[]byte{0x89, 0x50, 0x4E, 0x47}, true},
{[]byte{0xFF, 0x0, 0x0}, false},
}
for _, test := range cases {
if match := MatchesMap(test.buf, matchers.Image); match != test.match {
t.Fatalf("Do not matches: %#v", test.buf)
}
}
}

View File

@@ -39,5 +39,6 @@ func register(matchers ...Map) {
}
func init() {
// Arguments order is intentional
register(Image, Video, Audio, Font, Archive)
}

View File

@@ -1 +0,0 @@
package matchers

View File

@@ -1,4 +1,3 @@
package types
var Empty = NewType("", "")
var Unknown = NewType("unknown", "")

View File

@@ -6,6 +6,7 @@ type MIME struct {
Value string
}
// Creates a new MIME type
func NewMIME(mime string) MIME {
kind, subtype := splitMime(mime)
return MIME{Type: kind, Subtype: subtype, Value: mime}

View File

@@ -1 +1,27 @@
package types
import "testing"
func TestSplit(t *testing.T) {
cases := []struct {
mime string
kind string
subtype string
}{
{"image/jpeg", "image", "jpeg"},
{"/jpeg", "", "jpeg"},
{"image/", "image", ""},
{"/", "", ""},
{"image", "image", ""},
}
for _, test := range cases {
kind, subtype := splitMime(test.mime)
if test.kind != kind {
t.Fatalf("Invalid kind: %s", test.kind)
}
if test.subtype != subtype {
t.Fatalf("Invalid subtype: %s", test.subtype)
}
}
}

View File

@@ -2,7 +2,17 @@ package types
var Types = make(map[string]Type)
// Register a new type
func Add(t Type) Type {
Types[t.Extension] = t
return t
}
// Retrieve a Type by extension
func Get(ext string) Type {
kind := Types[ext]
if kind.Extension != "" {
return kind
}
return Unknown
}