diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..000dc0a --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3cf2565 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/bimg +/bundle +bin +/*.jpg +/*.png +/*.webp +/fixtures/*_out.* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4444bd4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: go +go: + - 1.5 + - 1.4 + - 1.3 + - release + - tip diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..30ede59 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6f86951 --- /dev/null +++ b/README.md @@ -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 diff --git a/filetype.go b/filetype.go new file mode 100644 index 0000000..f541b24 --- /dev/null +++ b/filetype.go @@ -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 +} diff --git a/filetype_test.go b/filetype_test.go new file mode 100644 index 0000000..24265be --- /dev/null +++ b/filetype_test.go @@ -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) + } + } +} diff --git a/matchers/archive.go b/matchers/archive.go new file mode 100644 index 0000000..4974c7c --- /dev/null +++ b/matchers/archive.go @@ -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 +} diff --git a/matchers/audio.go b/matchers/audio.go new file mode 100644 index 0000000..3704f94 --- /dev/null +++ b/matchers/audio.go @@ -0,0 +1,54 @@ +package matchers + +var TypeMidi = NewType("mid", "audio/midi") +var TypeMp3 = NewType("mp3", "audio/mpeg") +var TypeM4a = NewType("m4a", "audio/m4a") +var TypeOgg = NewType("ogg", "audio/ogg") +var TypeFlac = NewType("flac", "audio/x-flac") +var TypeWav = NewType("wav", "audio/x-wav") + +var Audio = Map{ + TypeMidi: Midi, + TypeMp3: Mp3, + TypeM4a: M4a, + TypeOgg: Ogg, + TypeFlac: Flac, + TypeWav: Wav, +} + +func Midi(buf []byte, length int) bool { + return length > 3 && + buf[0] == 0x4D && buf[1] == 0x54 && + buf[2] == 0x68 && buf[3] == 0x64 +} + +func Mp3(buf []byte, length int) bool { + return length > 2 && + (buf[0] == 0x49 && buf[1] == 0x44 && buf[2] == 0x33) || + (buf[0] == 0xFF && buf[1] == 0xfb) +} + +func M4a(buf []byte, length int) bool { + return length > 10 && + (buf[4] == 0x66 && buf[5] == 0x74 && buf[6] == 0x79 && + buf[7] == 0x70 && buf[8] == 0x4D && buf[9] == 0x34 && buf[10] == 0x41) || + (buf[0] == 0x4D && buf[1] == 0x34 && buf[2] == 0x41 && buf[3] == 0x20) +} + +func Ogg(buf []byte, length int) bool { + return length > 3 && + buf[0] == 0x4F && buf[1] == 0x67 && + buf[2] == 0x67 && buf[3] == 0x53 +} + +func Flac(buf []byte, length int) bool { + return length > 3 && + buf[0] == 0x66 && buf[1] == 0x4C && + buf[2] == 0x61 && buf[3] == 0x43 +} + +func Wav(buf []byte, length int) bool { + return length > 11 && + buf[0] == 0x52 && buf[1] == 0x49 && buf[2] == 0x46 && buf[3] == 0x46 && + buf[8] == 0x57 && buf[9] == 0x41 && buf[10] == 0x56 && buf[11] == 0x45 +} diff --git a/matchers/fonts.go b/matchers/fonts.go new file mode 100644 index 0000000..6a6e6c6 --- /dev/null +++ b/matchers/fonts.go @@ -0,0 +1,39 @@ +package matchers + +var TypeWoff = NewType("woff", "application/font-woff") +var TypeWoff2 = NewType("woff2", "application/font-woff") +var TypeTtf = NewType("ttf", "application/font-sfnt") +var TypeOtf = NewType("otf", "application/font-sfnt") + +var Font = Map{ + TypeWoff: Woff, + TypeWoff2: Woff2, + TypeTtf: Ttf, + TypeOtf: Otf, +} + +func Woff(buf []byte, length int) bool { + return length > 7 && + buf[0] == 0x77 && buf[1] == 0x4F && buf[2] == 0x46 && buf[3] == 0x46 && + buf[4] == 0x00 && buf[5] == 0x01 && buf[6] == 0x00 && buf[7] == 0x00 +} + +func Woff2(buf []byte, length int) bool { + return length > 7 && + buf[0] == 0x77 && buf[1] == 0x4F && buf[2] == 0x46 && buf[3] == 0x32 && + buf[4] == 0x00 && buf[5] == 0x01 && buf[6] == 0x00 && buf[7] == 0x00 +} + +func Ttf(buf []byte, length int) bool { + return length > 4 && + buf[0] == 0x00 && buf[1] == 0x01 && + buf[2] == 0x00 && buf[3] == 0x00 && + buf[4] == 0x00 +} + +func Otf(buf []byte, length int) bool { + return length > 4 && + buf[0] == 0x4F && buf[1] == 0x54 && + buf[2] == 0x54 && buf[3] == 0x4F && + buf[4] == 0x00 +} diff --git a/matchers/image.go b/matchers/image.go new file mode 100644 index 0000000..eb880d8 --- /dev/null +++ b/matchers/image.go @@ -0,0 +1,82 @@ +package matchers + +var TypeJpeg = NewType("jpg", "image/jpeg") +var TypePng = NewType("png", "image/png") +var TypeGif = NewType("gif", "image/gif") +var TypeWebp = NewType("webp", "image/webp") +var TypeCR2 = NewType("cr2", "image/x-canon-cr2") +var TypeTiff = NewType("tif", "image/tiff") +var TypeBmp = NewType("bmp", "image/bmp") +var TypeJxr = NewType("jxr", "image/vnd.ms-photo") +var TypePsd = NewType("psd", "image/vnd.adobe.photoshop") +var TypeIco = NewType("ico", "image/x-icon") + +var Image = Map{ + TypeJpeg: Jpeg, + TypePng: Png, + TypeGif: Gif, + TypeWebp: Webp, + TypeCR2: CR2, + TypeTiff: Tiff, + TypeBmp: Bmp, + TypeJxr: Jxr, + TypePsd: Psd, + TypeIco: Ico, +} + +func Jpeg(buf []byte, length int) bool { + return length > 2 && + buf[0] == 0xFF && buf[1] == 0xD8 && buf[2] == 0xFF +} + +func Png(buf []byte, length int) bool { + return length > 3 && + buf[0] == 0x89 && buf[1] == 0x50 && + buf[2] == 0x4E && buf[3] == 0x47 +} + +func Gif(buf []byte, length int) bool { + return length > 2 && + buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46 +} + +func Webp(buf []byte, length int) bool { + return length > 11 && + buf[8] == 0x57 && buf[9] == 0x45 && + buf[10] == 0x42 && buf[11] == 0x50 +} + +func CR2(buf []byte, length int) bool { + return length > 9 && + ((buf[0] == 0x49 && buf[1] == 0x49 && buf[2] == 0x2A && buf[3] == 0x0) || + (buf[0] == 0x4D && buf[1] == 0x4D && buf[2] == 0x0 && buf[3] == 0x2A)) && + buf[8] == 0x43 && buf[9] == 0x52 +} + +func Tiff(buf []byte, length int) bool { + return length > 3 && + (buf[0] == 0x49 && buf[1] == 0x49 && buf[2] == 0x2A && buf[3] == 0x0) || + (buf[0] == 0x4D && buf[1] == 0x4D && buf[2] == 0x0 && buf[3] == 0x2A) +} + +func Bmp(buf []byte, length int) bool { + return length > 1 && + buf[0] == 0x42 && buf[1] == 0x4D +} + +func Jxr(buf []byte, length int) bool { + return length > 2 && + buf[0] == 0x49 && buf[1] == 0x49 && buf[2] == 0xBC +} + +func Psd(buf []byte, length int) bool { + return length > 3 && + buf[0] == 0x38 && buf[1] == 0x42 && + buf[2] == 0x50 && buf[3] == 0x53 +} + +func Ico(buf []byte, length int) bool { + return length > 3 && + buf[0] == 0x00 && buf[1] == 0x00 && + buf[2] == 0x01 && buf[3] == 0x00 +} diff --git a/matchers/matcher.go b/matchers/matcher.go new file mode 100644 index 0000000..db3957b --- /dev/null +++ b/matchers/matcher.go @@ -0,0 +1,80 @@ +package matchers + +import "gopkg.in/h2non/filetype.v0/types" + +type Map map[types.Type]Matcher + +// Matcher function interface as type alias +type Matcher func([]byte, int) bool + +// Type specific matcher function interface +type TypeMatcher func([]byte, int) types.Type + +// Store registered file type matchers +var Matchers = make(map[types.Type]TypeMatcher) + +var NewType = types.NewType + +// Create and register a new type matcher function +func NewMatcher(kind types.Type, fn Matcher) TypeMatcher { + matcher := func(buf []byte, length int) types.Type { + if fn(buf, length) { + return kind + } + return types.Unknown + } + Matchers[kind] = matcher + return matcher +} + +// Match the file type of a given buffer +func Match(buf []byte) (types.Type, error) { + length := len(buf) + if length == 0 { + return types.Empty, nil + } + + for _, checker := range Matchers { + match := checker(buf, length) + if match != types.Unknown && match.Extension != "" { + return match, nil + } + } + + return types.Unknown, nil +} + +func MatchType(buf []byte, kind types.Type) bool { + return true +} + +func MatchMap(buf []byte, matchers Map) types.Type { + length := len(buf) + for kind, matcher := range matchers { + if matcher(buf, length) { + return kind + } + } + return types.Unknown +} + +func MatchesMap(buf []byte, matchers Map) bool { + return MatchMap(buf, matchers) != types.Unknown +} + +func Matches(buf []byte) bool { + kind, _ := Match(buf) + return kind != types.Unknown +} + +func register(matchers ...Map) { + for _, m := range matchers { + for kind, matcher := range m { + NewMatcher(kind, matcher) + } + } +} + +func init() { + register(Image, Video, Audio, Font, Archive) +} diff --git a/matchers/video.go b/matchers/video.go new file mode 100644 index 0000000..08e4f5a --- /dev/null +++ b/matchers/video.go @@ -0,0 +1,97 @@ +package matchers + +var TypeMp4 = NewType("mp4", "video/mp4") +var TypeM4v = NewType("m4v", "video/x-m4v") +var TypeMkv = NewType("mkv", "video/x-matroska") +var TypeWebm = NewType("webm", "video/webm") +var TypeMov = NewType("mov", "video/quicktime") +var TypeAvi = NewType("avi", "video/x-msvideo") +var TypeWmv = NewType("wmv", "video/x-ms-wmv") +var TypeMpeg = NewType("mpg", "video/mpeg") +var TypeFlv = NewType("flv", "video/x-flv") + +var Video = Map{ + TypeMp4: Mp4, + TypeM4v: M4v, + TypeMkv: Mkv, + TypeMov: Mov, + TypeAvi: Avi, + TypeWmv: Wmv, + TypeMpeg: Mpeg, + TypeFlv: Flv, +} + +func M4v(buf []byte, length int) bool { + return length > 10 && + buf[0] == 0x0 && buf[1] == 0x0 && + buf[2] == 0x0 && buf[3] == 0x1C && + buf[4] == 0x66 && buf[5] == 0x74 && + buf[6] == 0x79 && buf[7] == 0x70 && + buf[8] == 0x4D && buf[9] == 0x34 && + buf[10] == 0x56 +} + +func Mkv(buf []byte, length int) bool { + return length > 38 && + buf[31] == 0x6D && buf[32] == 0x61 && + buf[33] == 0x74 && buf[34] == 0x72 && + buf[35] == 0x6f && buf[36] == 0x73 && + buf[37] == 0x6B && buf[38] == 0x61 +} + +func Webm(buf []byte, length int) bool { + return length > 3 && + buf[0] == 0x1A && buf[1] == 0x45 && + buf[2] == 0xDF && buf[3] == 0xA3 +} + +func Mov(buf []byte, length int) bool { + return length > 7 && + buf[0] == 0x0 && buf[1] == 0x0 && + buf[2] == 0x0 && buf[3] == 0x14 && + buf[4] == 0x66 && buf[5] == 0x74 && + buf[6] == 0x79 && buf[7] == 0x70 +} + +func Avi(buf []byte, length int) bool { + return length > 10 && + buf[0] == 0x52 && buf[1] == 0x49 && + buf[2] == 0x46 && buf[3] == 0x46 && + buf[8] == 0x41 && buf[9] == 0x56 && + buf[10] == 0x49 +} + +func Wmv(buf []byte, length int) bool { + return length > 9 && + buf[0] == 0x30 && buf[1] == 0x26 && + buf[2] == 0xB2 && buf[3] == 0x75 && + buf[4] == 0x8E && buf[5] == 0x66 && + buf[6] == 0xCF && buf[7] == 0x11 && + buf[8] == 0xA6 && buf[9] == 0xD9 +} + +func Mpeg(buf []byte, length int) bool { + return length > 3 && + buf[0] == 0x0 && buf[1] == 0x0 && buf[2] == 0x1 && + buf[3] >= 0xb0 && buf[3] <= 0xbf +} + +func Flv(buf []byte, length int) bool { + return length > 3 && + buf[0] == 0x46 && buf[1] == 0x4C && + buf[2] == 0x56 && buf[3] == 0x01 +} + +func Mp4(buf []byte, length int) bool { + return length > 27 && + (buf[0] == 0x0 && buf[1] == 0x0 && buf[2] == 0x0 && + (buf[3] == 0x18 || buf[3] == 0x20) && buf[4] == 0x66 && + buf[5] == 0x74 && buf[6] == 0x79 && buf[7] == 0x70) || + (buf[0] == 0x33 && buf[1] == 0x67 && buf[2] == 0x70 && buf[3] == 0x35) || + (buf[0] == 0x0 && buf[1] == 0x0 && buf[2] == 0x0 && buf[3] == 0x1C && + buf[4] == 0x66 && buf[5] == 0x74 && buf[6] == 0x79 && buf[7] == 0x70 && + buf[8] == 0x6D && buf[9] == 0x70 && buf[10] == 0x34 && buf[11] == 0x32 && + buf[16] == 0x6D && buf[17] == 0x70 && buf[18] == 0x34 && buf[19] == 0x31 && + buf[20] == 0x6D && buf[21] == 0x70 && buf[22] == 0x34 && buf[23] == 0x32 && + buf[24] == 0x69 && buf[25] == 0x73 && buf[26] == 0x6F && buf[27] == 0x6D) +} diff --git a/types/defaults.go b/types/defaults.go new file mode 100644 index 0000000..b3dc36c --- /dev/null +++ b/types/defaults.go @@ -0,0 +1,4 @@ +package types + +var Empty = NewType("", "") +var Unknown = NewType("unknown", "") diff --git a/types/mime.go b/types/mime.go new file mode 100644 index 0000000..ca95110 --- /dev/null +++ b/types/mime.go @@ -0,0 +1,12 @@ +package types + +type MIME struct { + Type string + Subtype string + Value string +} + +func NewMIME(mime string) MIME { + kind, subtype := splitMime(mime) + return MIME{Type: kind, Subtype: subtype, Value: mime} +} diff --git a/types/split.go b/types/split.go new file mode 100644 index 0000000..68a5a8b --- /dev/null +++ b/types/split.go @@ -0,0 +1,11 @@ +package types + +import "strings" + +func splitMime(s string) (string, string) { + x := strings.Split(s, "/") + if len(x) > 1 { + return x[0], x[1] + } + return x[0], "" +} diff --git a/types/type.go b/types/type.go new file mode 100644 index 0000000..238df75 --- /dev/null +++ b/types/type.go @@ -0,0 +1,14 @@ +package types + +type Type struct { + MIME MIME + Extension string +} + +func NewType(ext, mime string) Type { + t := Type{ + MIME: NewMIME(mime), + Extension: ext, + } + return Add(t) +} diff --git a/types/types.go b/types/types.go new file mode 100644 index 0000000..97e347e --- /dev/null +++ b/types/types.go @@ -0,0 +1,8 @@ +package types + +var Types = make(map[string]Type) + +func Add(t Type) Type { + Types[t.Extension] = t + return t +}