Replaced h2non/filetype with net/http DetectContentType()

This commit is contained in:
Seednode 2023-09-11 16:05:38 -05:00
parent c06b536123
commit a3f8a85d28
33 changed files with 43 additions and 1905 deletions

View File

@ -12,7 +12,7 @@ import (
) )
const ( const (
Version string = "0.66.2" Version string = "0.67.0"
) )
var ( var (

View File

@ -6,8 +6,6 @@ package formats
import ( import (
"fmt" "fmt"
"github.com/h2non/filetype"
) )
func RegisterAudioFormats() *SupportedFormat { func RegisterAudioFormats() *SupportedFormat {
@ -25,10 +23,13 @@ func RegisterAudioFormats() *SupportedFormat {
Extensions: []string{ Extensions: []string{
`.mp3`, `.mp3`,
`.ogg`, `.ogg`,
`.oga`,
`.wav`, `.wav`,
}, },
validator: func(head []byte) bool { MimeTypes: []string{
return filetype.IsAudio(head) `audio/mpeg`,
`audio/ogg`,
`audio/wav`,
}, },
} }
} }

View File

@ -13,7 +13,6 @@ import (
_ "image/png" _ "image/png"
"os" "os"
"github.com/h2non/filetype"
_ "golang.org/x/image/bmp" _ "golang.org/x/image/bmp"
_ "golang.org/x/image/webp" _ "golang.org/x/image/webp"
) )
@ -53,13 +52,17 @@ func RegisterImageFormats() *SupportedFormat {
Extensions: []string{ Extensions: []string{
`.bmp`, `.bmp`,
`.gif`, `.gif`,
`.jpeg`,
`.jpg`, `.jpg`,
`.jpeg`,
`.png`, `.png`,
`.webp`, `.webp`,
}, },
validator: func(head []byte) bool { MimeTypes: []string{
return filetype.IsImage(head) `image/bmp`,
`image/gif`,
`image/jpeg`,
`image/png`,
`image/webp`,
}, },
} }
} }
@ -76,7 +79,7 @@ func ImageDimensions(path string) (*Dimensions, error) {
} }
defer file.Close() defer file.Close()
myImage, _, err := image.DecodeConfig(file) decodedConfig, _, err := image.DecodeConfig(file)
switch { switch {
case errors.Is(err, image.ErrFormat): case errors.Is(err, image.ErrFormat):
fmt.Printf("File %s has invalid image format\n", path) fmt.Printf("File %s has invalid image format\n", path)
@ -86,5 +89,5 @@ func ImageDimensions(path string) (*Dimensions, error) {
return &Dimensions{}, err return &Dimensions{}, err
} }
return &Dimensions{Width: myImage.Width, Height: myImage.Height}, nil return &Dimensions{Width: decodedConfig.Width, Height: decodedConfig.Height}, nil
} }

View File

@ -6,21 +6,17 @@ package formats
import ( import (
"errors" "errors"
"net/http"
"os" "os"
"path/filepath"
"strings"
"github.com/h2non/filetype"
) )
type FormatFunction func(queryParams, fileUri, filePath, fileName, mime string) string type FormatFunction func(queryParams, fileUri, filePath, fileName, mime string) string
type ValidatorFunction func([]byte) bool
type SupportedFormat struct { type SupportedFormat struct {
Title FormatFunction Title FormatFunction
Body FormatFunction Body FormatFunction
Extensions []string Extensions []string
validator ValidatorFunction MimeTypes []string
} }
type SupportedFormats struct { type SupportedFormats struct {
@ -32,19 +28,29 @@ func (s *SupportedFormats) Add(t *SupportedFormat) {
} }
func (s *SupportedFormats) Extensions() []string { func (s *SupportedFormats) Extensions() []string {
var r []string var extensions []string
for _, t := range s.types { for _, t := range s.types {
r = append(r, t.Extensions...) extensions = append(extensions, t.Extensions...)
} }
return r return extensions
} }
func (s *SupportedFormats) Type(extension string) *SupportedFormat { func (s *SupportedFormats) MimeTypes() []string {
var mimeTypes []string
for _, t := range s.types {
mimeTypes = append(mimeTypes, t.MimeTypes...)
}
return mimeTypes
}
func (s *SupportedFormats) Type(mimeType string) *SupportedFormat {
for i := range s.types { for i := range s.types {
for _, e := range s.types[i].Extensions { for _, m := range s.types[i].MimeTypes {
if extension == e { if mimeType == m {
return s.types[i] return s.types[i]
} }
} }
@ -53,18 +59,6 @@ func (s *SupportedFormats) Type(extension string) *SupportedFormat {
return nil return nil
} }
func (s *SupportedFormats) IsSupported(head []byte) bool {
r := false
for i := range s.types {
if s.types[i].validator(head) {
r = true
}
}
return r
}
func FileType(path string, types *SupportedFormats) (bool, *SupportedFormat, string, error) { func FileType(path string, types *SupportedFormats) (bool, *SupportedFormat, string, error) {
file, err := os.Open(path) file, err := os.Open(path)
switch { switch {
@ -75,20 +69,16 @@ func FileType(path string, types *SupportedFormats) (bool, *SupportedFormat, str
} }
defer file.Close() defer file.Close()
head := make([]byte, 261) head := make([]byte, 512)
file.Read(head) file.Read(head)
if types.IsSupported(head) { mimeType := http.DetectContentType(head)
extension := filepath.Ext(path)
for _, v := range types.Extensions() { for _, v := range types.MimeTypes() {
if extension == v { if mimeType == v {
fileType := types.Type(extension) fileType := types.Type(mimeType)
mimeType := (filetype.GetType(strings.TrimPrefix(extension, "."))).MIME.Value return true, fileType, mimeType, nil
return true, fileType, mimeType, nil
}
} }
} }

View File

@ -6,8 +6,6 @@ package formats
import ( import (
"fmt" "fmt"
"github.com/h2non/filetype"
) )
func RegisterVideoFormats() *SupportedFormat { func RegisterVideoFormats() *SupportedFormat {
@ -27,8 +25,10 @@ func RegisterVideoFormats() *SupportedFormat {
`.ogv`, `.ogv`,
`.webm`, `.webm`,
}, },
validator: func(head []byte) bool { MimeTypes: []string{
return filetype.IsVideo(head) `video/mp4`,
`video/ogg`,
`video/webm`,
}, },
} }
} }

1
go.mod
View File

@ -3,7 +3,6 @@ module seedno.de/seednode/roulette
go 1.21 go 1.21
require ( require (
github.com/h2non/filetype v1.1.3
github.com/julienschmidt/httprouter v1.3.0 github.com/julienschmidt/httprouter v1.3.0
github.com/klauspost/compress v1.16.7 github.com/klauspost/compress v1.16.7
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.7.0

2
go.sum
View File

@ -1,6 +1,4 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=

View File

@ -1,24 +0,0 @@
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.

View File

@ -1,12 +0,0 @@
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

View File

@ -1,2 +0,0 @@
bin
.DS_Store

View File

@ -1,16 +0,0 @@
language: go
arch:
- AMD64
- ppc64le
go:
- "1.13"
- "1.14"
before_install:
- go get -u -v golang.org/x/lint/golint
script:
- diff -u <(echo -n) <(gofmt -s -d ./)
- diff -u <(echo -n) <(go vet ./...)
- diff -u <(echo -n) <(golint)
- go test -v -race ./...

View File

@ -1,163 +0,0 @@
v1.0.3 / 2021-11-21
===================
* fix(#108): add application file matchers
* Merge pull request #106 from hannesbraun/aiff-support
* Add AIFF support
* fix(archive): format issue indentation
* feat(version): bump patch
* Merge pull request #100 from da2018/master
* Enhance Zstd support
* Merge pull request #98 from cfergeau/zstd
* Add zstd support
* Merge pull request #99 from cfergeau/byteprefixmatcher
* Introduce bytePrefixMatcher helper
v1.1.0 / 2020-06-06
===================
* feat: version bump v1.10
* feat(ci): add go 1.14
* Merge pull request #82 from andrewstucki/sqlite-update
* Merge pull request #84 from evanoberholster/master
* Better differentiation: between image/x-canon-cr2 and image/tiff
* Merge pull request #1 from h2non/master
* Update ico filetype per https://www.iana.org/assignments/media-types/image/vnd.microsoft.icon
* Update rar filetype per https://www.iana.org/assignments/media-types/application/vnd.rar
* Update exe filetype per https://www.iana.org/assignments/media-types/application/vnd.microsoft.portable-executable
* Update deb filetype per https://www.iana.org/assignments/media-types/application/vnd.debian.binary-package
* Update sqlite filetype per https://www.iana.org/assignments/media-types/application/vnd.sqlite3
* Merge pull request #72 from turn88/master
* Update document.go
* Update document.go
* Update document.go
* add matchers for office 2003
v1.0.10 / 2019-08-06
====================
* Merge pull request #76 from lex-r/fix-matroska-detection
* fix: mkv and webm types detection
v1.0.9 / 2019-07-25
===================
* Merge pull request #75 from Trane9991/master
* add video/3gpp support
* fix: use proper iso file mime type
* feat: add iso image format
* Merge pull request #65 from Fentonz/master
* Merge pull request #70 from fanpei91/master
* add image/vnd.dwg to README
* add image/vnd.dwg support
* Added support for .iso files
v1.0.8 / 2019-02-10
===================
* refactor(images): heic -> heif
* feat(docs): add heif format
* Merge pull request #60 from rikonor/master
* add heif/heic support
* fix(docs): dicom -> dcm
* feat: add dicom type
* Merge pull request #58 from Fentonz/master
* Merge pull request #59 from kmanley/master
* fix example; related to h2non/filetype#43
* Added DICOM type to archive
v1.0.7 / 2019-02-09
===================
* Merge pull request #56 from akupila/wasm
* add wasm to readme
* detect wasm file type
v1.0.6 / 2019-01-22
===================
* Merge pull request #55 from ivanlemeshev/master
* Added ftypmp4v to MP4 matcher
* Merge pull request #54 from aofei/master
* chore: add support for Go modules
* feat: add support for AAC (audio/aac)
* Merge pull request #53 from lynxbyorion/check-for-docoments
* Added checks for documents.
* Merge pull request #51 from eriken/master
* fixed bad mime and import paths
* Merge pull request #50 from eriken/jpeg2000_support
* fix import paths
* jpeg2000 support
* Merge pull request #47 from Ma124/master
* Merge pull request #49 from amoore614/master
* more robust check for .mov files
* bugfix: reverse order of matcher key list so user registered matchers appear first
* bugfix: store ptr to MatcherKeys in case user registered matchers are used.
* update comment
* Bump buffer size to 8K to allow for more custom file matching
* refactor(readme): update package import path
* Merge pull request #48 from kumakichi/support_msooxml
* do not use v1
* ok, master already changed travis
* add fixtures, but MatchReader may not work for some msooxml files, 4096 bytes maybe not enough
* support ms ooxml, #40
* Fixed misspells
* fix(travis): use string notation for matrix items
* Merge pull request #42 from bruth/patch-2
* refactor(travis): remove Go 1.6, add Go 1.10
* Change maximum bytes required for detection
* Merge pull request #36 from yiiTT/patch-1
* Add MP4 dash and additional ISO formats
* Merge pull request #34 from RangelReale/fix-mp4-case
* Merge pull request #32 from yiiTT/fix-m4v
* Fixed mp4 detection case-sensitivity according to http://www.ftyps.com/
* Fix M4v matcher
v1.0.5 / 2017-12-12
===================
* Merge pull request #30 from RangelReale/fix_mp4
* Fix duplicated item in mp4 fix
* Fix MP4 matcher, with information from http://www.file-recovery.com/mp4-signature-format.htm
* Merge pull request #28 from ikovic/master
* Updated file header example.
v1.0.4 / 2017-11-29
===================
* fix: tests and document types matchers
* refactor(docs): remove codesponsor
* Merge pull request #26 from bienkma/master
* Add support check file type: .doc, .docx, .pptx, .ppt, .xls, .xlsx
* feat(docs): add code sponsor banner
* feat(travis): add go 1.9
* Merge pull request #24 from strazzere/patch-1
* Fix typo in unknown
v1.0.3 / 2017-08-03
===================
* Merge pull request #21 from elemeta/master
* Add Elf file as supported matcher archive type
v1.0.2 / 2017-07-26
===================
* Merge pull request #20 from marshyski/master
* Added RedHat RPM as supported matcher archive type
* Merge pull request #19 from nlamirault/patch-1
* Fix typo in documentation
v1.0.1 / 2017-02-24
===================
* Merge pull request #18 from Impyy/enable-webm
* Enable the webm matcher
* feat(docs): add Go version badge
1.0.0 / 2016-12-11
==================
- Initial stable version (v1.0.0).

View File

@ -1,24 +0,0 @@
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.

View File

@ -1,294 +0,0 @@
# filetype [![Build Status](https://travis-ci.org/h2non/filetype.svg)](https://travis-ci.org/h2non/filetype) [![GoDoc](https://godoc.org/github.com/h2non/filetype?status.svg)](https://godoc.org/github.com/h2non/filetype) [![Go Report Card](http://goreportcard.com/badge/h2non/filetype)](http://goreportcard.com/report/h2non/filetype) [![Go Version](https://img.shields.io/badge/go-v1.0+-green.svg?style=flat)](https://github.com/h2non/gentleman)
Small and dependency free [Go](https://golang.org) package to infer file and MIME type checking the [magic numbers](<https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files>) signature.
For SVG file type checking, see [go-is-svg](https://github.com/h2non/go-is-svg) package. Python port: [filetype.py](https://github.com/h2non/filetype.py).
## 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...)
- Provides a bunch of helpers and file matching shortcuts
- [Pluggable](#add-additional-file-type-matchers): add custom new types and matchers
- Simple and semantic API
- [Blazing fast](#benchmarks), even processing large files
- Only first 262 bytes representing the max file header is required, so you can just [pass a slice](#file-header)
- Dependency free (just Go code, no C compilation needed)
- Cross-platform file recognition
## Installation
```bash
go get github.com/h2non/filetype
```
## API
See [Godoc](https://godoc.org/github.com/h2non/filetype) reference.
### Subpackages
- [`github.com/h2non/filetype/types`](https://godoc.org/github.com/h2non/filetype/types)
- [`github.com/h2non/filetype/matchers`](https://godoc.org/github.com/h2non/filetype/matchers)
## Examples
#### Simple file type checking
```go
package main
import (
"fmt"
"io/ioutil"
"github.com/h2non/filetype"
)
func main() {
buf, _ := ioutil.ReadFile("sample.jpg")
kind, _ := filetype.Match(buf)
if kind == filetype.Unknown {
fmt.Println("Unknown file type")
return
}
fmt.Printf("File type: %s. MIME: %s\n", kind.Extension, kind.MIME.Value)
}
```
#### Check type class
```go
package main
import (
"fmt"
"io/ioutil"
"github.com/h2non/filetype"
)
func main() {
buf, _ := ioutil.ReadFile("sample.jpg")
if filetype.IsImage(buf) {
fmt.Println("File is an image")
} else {
fmt.Println("Not an image")
}
}
```
#### Supported type
```go
package main
import (
"fmt"
"github.com/h2non/filetype"
)
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")
}
}
```
#### File header
```go
package main
import (
"fmt"
"io/ioutil"
"github.com/h2non/filetype"
)
func main() {
// Open a file descriptor
file, _ := os.Open("movie.mp4")
// We only have to pass the file header = first 261 bytes
head := make([]byte, 261)
file.Read(head)
if filetype.IsImage(head) {
fmt.Println("File is an image")
} else {
fmt.Println("Not an image")
}
}
```
#### Add additional file type matchers
```go
package main
import (
"fmt"
"github.com/h2non/filetype"
)
var fooType = filetype.NewType("foo", "foo/foo")
func fooMatcher(buf []byte) bool {
return len(buf) > 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("New supported type: foo")
}
// Check if the new type is supported by MIME
if filetype.IsMIMESupported("foo/foo") {
fmt.Println("New supported MIME 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`
- **heif** - `image/heif`
- **jxr** - `image/vnd.ms-photo`
- **psd** - `image/vnd.adobe.photoshop`
- **ico** - `image/vnd.microsoft.icon`
- **dwg** - `image/vnd.dwg`
#### 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`
- **3gp** - `video/3gpp`
#### Audio
- **mid** - `audio/midi`
- **mp3** - `audio/mpeg`
- **m4a** - `audio/m4a`
- **ogg** - `audio/ogg`
- **flac** - `audio/x-flac`
- **wav** - `audio/x-wav`
- **amr** - `audio/amr`
- **aac** - `audio/aac`
- **aiff** - `audio/x-aiff`
#### Archive
- **epub** - `application/epub+zip`
- **zip** - `application/zip`
- **tar** - `application/x-tar`
- **rar** - `application/vnd.rar`
- **gz** - `application/gzip`
- **bz2** - `application/x-bzip2`
- **7z** - `application/x-7z-compressed`
- **xz** - `application/x-xz`
- **zstd** - `application/zstd`
- **pdf** - `application/pdf`
- **exe** - `application/vnd.microsoft.portable-executable`
- **swf** - `application/x-shockwave-flash`
- **rtf** - `application/rtf`
- **iso** - `application/x-iso9660-image`
- **eot** - `application/octet-stream`
- **ps** - `application/postscript`
- **sqlite** - `application/vnd.sqlite3`
- **nes** - `application/x-nintendo-nes-rom`
- **crx** - `application/x-google-chrome-extension`
- **cab** - `application/vnd.ms-cab-compressed`
- **deb** - `application/vnd.debian.binary-package`
- **ar** - `application/x-unix-archive`
- **Z** - `application/x-compress`
- **lz** - `application/x-lzip`
- **rpm** - `application/x-rpm`
- **elf** - `application/x-executable`
- **dcm** - `application/dicom`
#### Documents
- **doc** - `application/msword`
- **docx** - `application/vnd.openxmlformats-officedocument.wordprocessingml.document`
- **xls** - `application/vnd.ms-excel`
- **xlsx** - `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
- **ppt** - `application/vnd.ms-powerpoint`
- **pptx** - `application/vnd.openxmlformats-officedocument.presentationml.presentation`
#### Font
- **woff** - `application/font-woff`
- **woff2** - `application/font-woff`
- **ttf** - `application/font-sfnt`
- **otf** - `application/font-sfnt`
#### Application
- **wasm** - `application/wasm`
- **dex** - `application/vnd.android.dex`
- **dey** - `application/vnd.android.dey`
## Benchmarks
Measured using [real files](https://github.com/h2non/filetype/tree/master/fixtures).
Environment: OSX x64 i7 2.7 Ghz
```bash
BenchmarkMatchTar-8 1000000 1083 ns/op
BenchmarkMatchZip-8 1000000 1162 ns/op
BenchmarkMatchJpeg-8 1000000 1280 ns/op
BenchmarkMatchGif-8 1000000 1315 ns/op
BenchmarkMatchPng-8 1000000 1121 ns/op
```
## License
MIT - Tomas Aparicio

View File

@ -1,102 +0,0 @@
package filetype
import (
"errors"
"github.com/h2non/filetype/matchers"
"github.com/h2non/filetype/types"
)
// Types stores a map of supported types
var Types = types.Types
// NewType creates and registers a new type
var NewType = types.NewType
// Unknown represents an unknown file type
var Unknown = types.Unknown
// ErrEmptyBuffer represents an empty buffer error
var ErrEmptyBuffer = errors.New("Empty buffer")
// ErrUnknownBuffer represents a unknown buffer error
var ErrUnknownBuffer = errors.New("Unknown buffer type")
// AddType registers a new file type
func AddType(ext, mime string) types.Type {
return types.NewType(ext, mime)
}
// Is checks if a given buffer matches with the given file type extension
func Is(buf []byte, ext string) bool {
kind := types.Get(ext)
if kind != types.Unknown {
return IsType(buf, kind)
}
return false
}
// IsExtension semantic alias to Is()
func IsExtension(buf []byte, ext string) bool {
return Is(buf, ext)
}
// IsType 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
}
return matcher(buf) != types.Unknown
}
// IsMIME checks if a given buffer matches with the given MIME type
func IsMIME(buf []byte, mime string) bool {
result := false
types.Types.Range(func(k, v interface{}) bool {
kind := v.(types.Type)
if kind.MIME.Value == mime {
matcher := matchers.Matchers[kind]
result = matcher(buf) != types.Unknown
return false
}
return true
})
return result
}
// IsSupported checks if a given file extension is supported
func IsSupported(ext string) bool {
result := false
types.Types.Range(func(k, v interface{}) bool {
key := k.(string)
if key == ext {
result = true
return false
}
return true
})
return result
}
// IsMIMESupported checks if a given MIME type is supported
func IsMIMESupported(mime string) bool {
result := false
types.Types.Range(func(k, v interface{}) bool {
kind := v.(types.Type)
if kind.MIME.Value == mime {
result = true
return false
}
return true
})
return result
}
// GetType retrieves a Type by file extension
func GetType(ext string) types.Type {
return types.Get(ext)
}

View File

@ -1,91 +0,0 @@
package filetype
import (
"github.com/h2non/filetype/matchers"
"github.com/h2non/filetype/types"
)
// Image tries to match a file as image type
func Image(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Image)
}
// IsImage checks if the given buffer is an image type
func IsImage(buf []byte) bool {
kind, _ := Image(buf)
return kind != types.Unknown
}
// Audio tries to match a file as audio type
func Audio(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Audio)
}
// IsAudio checks if the given buffer is an audio type
func IsAudio(buf []byte) bool {
kind, _ := Audio(buf)
return kind != types.Unknown
}
// Video tries to match a file as video type
func Video(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Video)
}
// IsVideo checks if the given buffer is a video type
func IsVideo(buf []byte) bool {
kind, _ := Video(buf)
return kind != types.Unknown
}
// Font tries to match a file as text font type
func Font(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Font)
}
// IsFont checks if the given buffer is a font type
func IsFont(buf []byte) bool {
kind, _ := Font(buf)
return kind != types.Unknown
}
// Archive tries to match a file as generic archive type
func Archive(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Archive)
}
// IsArchive checks if the given buffer is an archive type
func IsArchive(buf []byte) bool {
kind, _ := Archive(buf)
return kind != types.Unknown
}
// Document tries to match a file as document type
func Document(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Document)
}
// IsDocument checks if the given buffer is an document type
func IsDocument(buf []byte) bool {
kind, _ := Document(buf)
return kind != types.Unknown
}
// Application tries to match a file as an application type
func Application(buf []byte) (types.Type, error) {
return doMatchMap(buf, matchers.Application)
}
// IsApplication checks if the given buffer is an application type
func IsApplication(buf []byte) bool {
kind, _ := Application(buf)
return kind != types.Unknown
}
func doMatchMap(buf []byte, machers matchers.Map) (types.Type, error) {
kind := MatchMap(buf, machers)
if kind != types.Unknown {
return kind, nil
}
return kind, ErrUnknownBuffer
}

View File

@ -1,90 +0,0 @@
package filetype
import (
"io"
"os"
"github.com/h2non/filetype/matchers"
"github.com/h2non/filetype/types"
)
// Matchers is an alias to matchers.Matchers
var Matchers = matchers.Matchers
// MatcherKeys is an alias to matchers.MatcherKeys
var MatcherKeys = &matchers.MatcherKeys
// NewMatcher is an alias to matchers.NewMatcher
var NewMatcher = matchers.NewMatcher
// Match infers the file type of a given buffer inspecting its magic numbers signature
func Match(buf []byte) (types.Type, error) {
length := len(buf)
if length == 0 {
return types.Unknown, ErrEmptyBuffer
}
for _, kind := range *MatcherKeys {
checker := Matchers[kind]
match := checker(buf)
if match != types.Unknown && match.Extension != "" {
return match, nil
}
}
return types.Unknown, nil
}
// Get is an alias to Match()
func Get(buf []byte) (types.Type, error) {
return Match(buf)
}
// MatchFile infers a file type for a file
func MatchFile(filepath string) (types.Type, error) {
file, err := os.Open(filepath)
if err != nil {
return types.Unknown, err
}
defer file.Close()
return MatchReader(file)
}
// MatchReader is convenient wrapper to Match() any Reader
func MatchReader(reader io.Reader) (types.Type, error) {
buffer := make([]byte, 8192) // 8K makes msooxml tests happy and allows for expanded custom file checks
_, err := reader.Read(buffer)
if err != nil && err != io.EOF {
return types.Unknown, err
}
return Match(buffer)
}
// AddMatcher registers a new matcher type
func AddMatcher(fileType types.Type, matcher matchers.Matcher) matchers.TypeMatcher {
return matchers.NewMatcher(fileType, matcher)
}
// Matches checks if the given buffer matches with some supported file type
func Matches(buf []byte) bool {
kind, _ := Match(buf)
return kind != types.Unknown
}
// MatchMap performs a file matching against a map of match functions
func MatchMap(buf []byte, matchers matchers.Map) types.Type {
for kind, matcher := range matchers {
if matcher(buf) {
return kind
}
}
return types.Unknown
}
// MatchesMap is an alias to Matches() but using matching against a map of match functions
func MatchesMap(buf []byte, matchers matchers.Map) bool {
return MatchMap(buf, matchers) != types.Unknown
}

View File

@ -1,43 +0,0 @@
package matchers
var (
TypeWasm = newType("wasm", "application/wasm")
TypeDex = newType("dex", "application/vnd.android.dex")
TypeDey = newType("dey", "application/vnd.android.dey")
)
var Application = Map{
TypeWasm: Wasm,
TypeDex: Dex,
TypeDey: Dey,
}
// Wasm detects a Web Assembly 1.0 filetype.
func Wasm(buf []byte) bool {
// WASM has starts with `\0asm`, followed by the version.
// http://webassembly.github.io/spec/core/binary/modules.html#binary-magic
return len(buf) >= 8 &&
buf[0] == 0x00 && buf[1] == 0x61 &&
buf[2] == 0x73 && buf[3] == 0x6D &&
buf[4] == 0x01 && buf[5] == 0x00 &&
buf[6] == 0x00 && buf[7] == 0x00
}
// Dex detects dalvik executable(DEX)
func Dex(buf []byte) bool {
// https://source.android.com/devices/tech/dalvik/dex-format#dex-file-magic
return len(buf) > 36 &&
// magic
buf[0] == 0x64 && buf[1] == 0x65 && buf[2] == 0x78 && buf[3] == 0x0A &&
// file sise
buf[36] == 0x70
}
// Dey Optimized Dalvik Executable(ODEX)
func Dey(buf []byte) bool {
return len(buf) > 100 &&
// dey magic
buf[0] == 0x64 && buf[1] == 0x65 && buf[2] == 0x79 && buf[3] == 0x0A &&
// dex
Dex(buf[40:100])
}

View File

@ -1,211 +0,0 @@
package matchers
import "encoding/binary"
const (
ZstdMagicSkippableStart = 0x184D2A50
ZstdMagicSkippableMask = 0xFFFFFFF0
)
var (
TypeEpub = newType("epub", "application/epub+zip")
TypeZip = newType("zip", "application/zip")
TypeTar = newType("tar", "application/x-tar")
TypeRar = newType("rar", "application/vnd.rar")
TypeGz = newType("gz", "application/gzip")
TypeBz2 = newType("bz2", "application/x-bzip2")
Type7z = newType("7z", "application/x-7z-compressed")
TypeXz = newType("xz", "application/x-xz")
TypeZstd = newType("zst", "application/zstd")
TypePdf = newType("pdf", "application/pdf")
TypeExe = newType("exe", "application/vnd.microsoft.portable-executable")
TypeSwf = newType("swf", "application/x-shockwave-flash")
TypeRtf = newType("rtf", "application/rtf")
TypeEot = newType("eot", "application/octet-stream")
TypePs = newType("ps", "application/postscript")
TypeSqlite = newType("sqlite", "application/vnd.sqlite3")
TypeNes = newType("nes", "application/x-nintendo-nes-rom")
TypeCrx = newType("crx", "application/x-google-chrome-extension")
TypeCab = newType("cab", "application/vnd.ms-cab-compressed")
TypeDeb = newType("deb", "application/vnd.debian.binary-package")
TypeAr = newType("ar", "application/x-unix-archive")
TypeZ = newType("Z", "application/x-compress")
TypeLz = newType("lz", "application/x-lzip")
TypeRpm = newType("rpm", "application/x-rpm")
TypeElf = newType("elf", "application/x-executable")
TypeDcm = newType("dcm", "application/dicom")
TypeIso = newType("iso", "application/x-iso9660-image")
TypeMachO = newType("macho", "application/x-mach-binary") // Mach-O binaries have no common extension.
)
var Archive = Map{
TypeEpub: bytePrefixMatcher(epubMagic),
TypeZip: Zip,
TypeTar: Tar,
TypeRar: Rar,
TypeGz: bytePrefixMatcher(gzMagic),
TypeBz2: bytePrefixMatcher(bz2Magic),
Type7z: bytePrefixMatcher(sevenzMagic),
TypeXz: bytePrefixMatcher(xzMagic),
TypeZstd: Zst,
TypePdf: bytePrefixMatcher(pdfMagic),
TypeExe: bytePrefixMatcher(exeMagic),
TypeSwf: Swf,
TypeRtf: bytePrefixMatcher(rtfMagic),
TypeEot: Eot,
TypePs: bytePrefixMatcher(psMagic),
TypeSqlite: bytePrefixMatcher(sqliteMagic),
TypeNes: bytePrefixMatcher(nesMagic),
TypeCrx: bytePrefixMatcher(crxMagic),
TypeCab: Cab,
TypeDeb: bytePrefixMatcher(debMagic),
TypeAr: bytePrefixMatcher(arMagic),
TypeZ: Z,
TypeLz: bytePrefixMatcher(lzMagic),
TypeRpm: Rpm,
TypeElf: Elf,
TypeDcm: Dcm,
TypeIso: Iso,
TypeMachO: MachO,
}
var (
epubMagic = []byte{
0x50, 0x4B, 0x03, 0x04, 0x6D, 0x69, 0x6D, 0x65,
0x74, 0x79, 0x70, 0x65, 0x61, 0x70, 0x70, 0x6C,
0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2F,
0x65, 0x70, 0x75, 0x62, 0x2B, 0x7A, 0x69, 0x70,
}
gzMagic = []byte{0x1F, 0x8B, 0x08}
bz2Magic = []byte{0x42, 0x5A, 0x68}
sevenzMagic = []byte{0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C}
pdfMagic = []byte{0x25, 0x50, 0x44, 0x46}
exeMagic = []byte{0x4D, 0x5A}
rtfMagic = []byte{0x7B, 0x5C, 0x72, 0x74, 0x66}
nesMagic = []byte{0x4E, 0x45, 0x53, 0x1A}
crxMagic = []byte{0x43, 0x72, 0x32, 0x34}
psMagic = []byte{0x25, 0x21}
xzMagic = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}
sqliteMagic = []byte{0x53, 0x51, 0x4C, 0x69}
debMagic = []byte{
0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A,
0x64, 0x65, 0x62, 0x69, 0x61, 0x6E, 0x2D, 0x62,
0x69, 0x6E, 0x61, 0x72, 0x79,
}
arMagic = []byte{0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E}
zstdMagic = []byte{0x28, 0xB5, 0x2F, 0xFD}
lzMagic = []byte{0x4C, 0x5A, 0x49, 0x50}
)
func bytePrefixMatcher(magicPattern []byte) Matcher {
return func(data []byte) bool {
return compareBytes(data, magicPattern, 0)
}
}
func Zip(buf []byte) bool {
return len(buf) > 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) bool {
return len(buf) > 261 &&
buf[257] == 0x75 && buf[258] == 0x73 &&
buf[259] == 0x74 && buf[260] == 0x61 &&
buf[261] == 0x72
}
func Rar(buf []byte) bool {
return len(buf) > 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 Swf(buf []byte) bool {
return len(buf) > 2 &&
(buf[0] == 0x43 || buf[0] == 0x46) &&
buf[1] == 0x57 && buf[2] == 0x53
}
func Cab(buf []byte) bool {
return len(buf) > 3 &&
((buf[0] == 0x4D && buf[1] == 0x53 && buf[2] == 0x43 && buf[3] == 0x46) ||
(buf[0] == 0x49 && buf[1] == 0x53 && buf[2] == 0x63 && buf[3] == 0x28))
}
func Eot(buf []byte) bool {
return len(buf) > 35 &&
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 Z(buf []byte) bool {
return len(buf) > 1 &&
((buf[0] == 0x1F && buf[1] == 0xA0) ||
(buf[0] == 0x1F && buf[1] == 0x9D))
}
func Rpm(buf []byte) bool {
return len(buf) > 96 &&
buf[0] == 0xED && buf[1] == 0xAB &&
buf[2] == 0xEE && buf[3] == 0xDB
}
func Elf(buf []byte) bool {
return len(buf) > 52 &&
buf[0] == 0x7F && buf[1] == 0x45 &&
buf[2] == 0x4C && buf[3] == 0x46
}
func Dcm(buf []byte) bool {
return len(buf) > 131 &&
buf[128] == 0x44 && buf[129] == 0x49 &&
buf[130] == 0x43 && buf[131] == 0x4D
}
func Iso(buf []byte) bool {
return len(buf) > 32773 &&
buf[32769] == 0x43 && buf[32770] == 0x44 &&
buf[32771] == 0x30 && buf[32772] == 0x30 &&
buf[32773] == 0x31
}
func MachO(buf []byte) bool {
return len(buf) > 3 && ((buf[0] == 0xFE && buf[1] == 0xED && buf[2] == 0xFA && buf[3] == 0xCF) ||
(buf[0] == 0xFE && buf[1] == 0xED && buf[2] == 0xFA && buf[3] == 0xCE) ||
(buf[0] == 0xBE && buf[1] == 0xBA && buf[2] == 0xFE && buf[3] == 0xCA) ||
// Big endian versions below here...
(buf[0] == 0xCF && buf[1] == 0xFA && buf[2] == 0xED && buf[3] == 0xFE) ||
(buf[0] == 0xCE && buf[1] == 0xFA && buf[2] == 0xED && buf[3] == 0xFE) ||
(buf[0] == 0xCA && buf[1] == 0xFE && buf[2] == 0xBA && buf[3] == 0xBE))
}
// Zstandard compressed data is made of one or more frames.
// There are two frame formats defined by Zstandard: Zstandard frames and Skippable frames.
// See more details from https://tools.ietf.org/id/draft-kucherawy-dispatch-zstd-00.html#rfc.section.2
func Zst(buf []byte) bool {
if compareBytes(buf, zstdMagic, 0) {
return true
} else {
// skippable frames
if len(buf) < 8 {
return false
}
if binary.LittleEndian.Uint32(buf[:4]) & ZstdMagicSkippableMask == ZstdMagicSkippableStart {
userDataLength := binary.LittleEndian.Uint32(buf[4:8])
if len(buf) < 8 + int(userDataLength) {
return false
}
nextFrame := buf[8+userDataLength:]
return Zst(nextFrame)
}
return false
}
}

View File

@ -1,85 +0,0 @@
package matchers
var (
TypeMidi = newType("mid", "audio/midi")
TypeMp3 = newType("mp3", "audio/mpeg")
TypeM4a = newType("m4a", "audio/m4a")
TypeOgg = newType("ogg", "audio/ogg")
TypeFlac = newType("flac", "audio/x-flac")
TypeWav = newType("wav", "audio/x-wav")
TypeAmr = newType("amr", "audio/amr")
TypeAac = newType("aac", "audio/aac")
TypeAiff = newType("aiff", "audio/x-aiff")
)
var Audio = Map{
TypeMidi: Midi,
TypeMp3: Mp3,
TypeM4a: M4a,
TypeOgg: Ogg,
TypeFlac: Flac,
TypeWav: Wav,
TypeAmr: Amr,
TypeAac: Aac,
TypeAiff: Aiff,
}
func Midi(buf []byte) bool {
return len(buf) > 3 &&
buf[0] == 0x4D && buf[1] == 0x54 &&
buf[2] == 0x68 && buf[3] == 0x64
}
func Mp3(buf []byte) bool {
return len(buf) > 2 &&
((buf[0] == 0x49 && buf[1] == 0x44 && buf[2] == 0x33) ||
(buf[0] == 0xFF && buf[1] == 0xfb))
}
func M4a(buf []byte) bool {
return len(buf) > 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) bool {
return len(buf) > 3 &&
buf[0] == 0x4F && buf[1] == 0x67 &&
buf[2] == 0x67 && buf[3] == 0x53
}
func Flac(buf []byte) bool {
return len(buf) > 3 &&
buf[0] == 0x66 && buf[1] == 0x4C &&
buf[2] == 0x61 && buf[3] == 0x43
}
func Wav(buf []byte) bool {
return len(buf) > 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
}
func Amr(buf []byte) bool {
return len(buf) > 11 &&
buf[0] == 0x23 && buf[1] == 0x21 &&
buf[2] == 0x41 && buf[3] == 0x4D &&
buf[4] == 0x52 && buf[5] == 0x0A
}
func Aac(buf []byte) bool {
return len(buf) > 1 &&
((buf[0] == 0xFF && buf[1] == 0xF1) ||
(buf[0] == 0xFF && buf[1] == 0xF9))
}
func Aiff(buf []byte) bool {
return len(buf) > 11 &&
buf[0] == 0x46 && buf[1] == 0x4F &&
buf[2] == 0x52 && buf[3] == 0x4D &&
buf[8] == 0x41 && buf[9] == 0x49 &&
buf[10] == 0x46 && buf[11] == 0x46
}

View File

@ -1,197 +0,0 @@
package matchers
import (
"bytes"
"encoding/binary"
)
var (
TypeDoc = newType("doc", "application/msword")
TypeDocx = newType("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
TypeXls = newType("xls", "application/vnd.ms-excel")
TypeXlsx = newType("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
TypePpt = newType("ppt", "application/vnd.ms-powerpoint")
TypePptx = newType("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation")
)
var Document = Map{
TypeDoc: Doc,
TypeDocx: Docx,
TypeXls: Xls,
TypeXlsx: Xlsx,
TypePpt: Ppt,
TypePptx: Pptx,
}
type docType int
const (
TYPE_DOC docType = iota
TYPE_DOCX
TYPE_XLS
TYPE_XLSX
TYPE_PPT
TYPE_PPTX
TYPE_OOXML
)
//reference: https://bz.apache.org/ooo/show_bug.cgi?id=111457
func Doc(buf []byte) bool {
if len(buf) > 513 {
return buf[0] == 0xD0 && buf[1] == 0xCF &&
buf[2] == 0x11 && buf[3] == 0xE0 &&
buf[512] == 0xEC && buf[513] == 0xA5
} else {
return len(buf) > 3 &&
buf[0] == 0xD0 && buf[1] == 0xCF &&
buf[2] == 0x11 && buf[3] == 0xE0
}
}
func Docx(buf []byte) bool {
typ, ok := msooxml(buf)
return ok && typ == TYPE_DOCX
}
func Xls(buf []byte) bool {
if len(buf) > 513 {
return buf[0] == 0xD0 && buf[1] == 0xCF &&
buf[2] == 0x11 && buf[3] == 0xE0 &&
buf[512] == 0x09 && buf[513] == 0x08
} else {
return len(buf) > 3 &&
buf[0] == 0xD0 && buf[1] == 0xCF &&
buf[2] == 0x11 && buf[3] == 0xE0
}
}
func Xlsx(buf []byte) bool {
typ, ok := msooxml(buf)
return ok && typ == TYPE_XLSX
}
func Ppt(buf []byte) bool {
if len(buf) > 513 {
return buf[0] == 0xD0 && buf[1] == 0xCF &&
buf[2] == 0x11 && buf[3] == 0xE0 &&
buf[512] == 0xA0 && buf[513] == 0x46
} else {
return len(buf) > 3 &&
buf[0] == 0xD0 && buf[1] == 0xCF &&
buf[2] == 0x11 && buf[3] == 0xE0
}
}
func Pptx(buf []byte) bool {
typ, ok := msooxml(buf)
return ok && typ == TYPE_PPTX
}
func msooxml(buf []byte) (typ docType, found bool) {
signature := []byte{'P', 'K', 0x03, 0x04}
// start by checking for ZIP local file header signature
if ok := compareBytes(buf, signature, 0); !ok {
return
}
// make sure the first file is correct
if v, ok := checkMSOoml(buf, 0x1E); ok {
return v, ok
}
if !compareBytes(buf, []byte("[Content_Types].xml"), 0x1E) &&
!compareBytes(buf, []byte("_rels/.rels"), 0x1E) &&
!compareBytes(buf, []byte("docProps"), 0x1E) {
return
}
// skip to the second local file header
// since some documents include a 520-byte extra field following the file
// header, we need to scan for the next header
startOffset := int(binary.LittleEndian.Uint32(buf[18:22]) + 49)
idx := search(buf, startOffset, 6000)
if idx == -1 {
return
}
// now skip to the *third* local file header; again, we need to scan due to a
// 520-byte extra field following the file header
startOffset += idx + 4 + 26
idx = search(buf, startOffset, 6000)
if idx == -1 {
return
}
// and check the subdirectory name to determine which type of OOXML
// file we have. Correct the mimetype with the registered ones:
// http://technet.microsoft.com/en-us/library/cc179224.aspx
startOffset += idx + 4 + 26
if typ, ok := checkMSOoml(buf, startOffset); ok {
return typ, ok
}
// OpenOffice/Libreoffice orders ZIP entry differently, so check the 4th file
startOffset += 26
idx = search(buf, startOffset, 6000)
if idx == -1 {
return TYPE_OOXML, true
}
startOffset += idx + 4 + 26
if typ, ok := checkMSOoml(buf, startOffset); ok {
return typ, ok
} else {
return TYPE_OOXML, true
}
}
func compareBytes(slice, subSlice []byte, startOffset int) bool {
sl := len(subSlice)
if startOffset+sl > len(slice) {
return false
}
s := slice[startOffset : startOffset+sl]
for i := range s {
if subSlice[i] != s[i] {
return false
}
}
return true
}
func checkMSOoml(buf []byte, offset int) (typ docType, ok bool) {
ok = true
switch {
case compareBytes(buf, []byte("word/"), offset):
typ = TYPE_DOCX
case compareBytes(buf, []byte("ppt/"), offset):
typ = TYPE_PPTX
case compareBytes(buf, []byte("xl/"), offset):
typ = TYPE_XLSX
default:
ok = false
}
return
}
func search(buf []byte, start, rangeNum int) int {
length := len(buf)
end := start + rangeNum
signature := []byte{'P', 'K', 0x03, 0x04}
if end > length {
end = length
}
if start >= end {
return -1
}
return bytes.Index(buf[start:end], signature)
}

View File

@ -1,45 +0,0 @@
package matchers
var (
TypeWoff = newType("woff", "application/font-woff")
TypeWoff2 = newType("woff2", "application/font-woff")
TypeTtf = newType("ttf", "application/font-sfnt")
TypeOtf = newType("otf", "application/font-sfnt")
)
var Font = Map{
TypeWoff: Woff,
TypeWoff2: Woff2,
TypeTtf: Ttf,
TypeOtf: Otf,
}
func Woff(buf []byte) bool {
return len(buf) > 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) bool {
return len(buf) > 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) bool {
return len(buf) > 4 &&
buf[0] == 0x00 && buf[1] == 0x01 &&
buf[2] == 0x00 && buf[3] == 0x00 &&
buf[4] == 0x00
}
func Otf(buf []byte) bool {
return len(buf) > 4 &&
buf[0] == 0x4F && buf[1] == 0x54 &&
buf[2] == 0x54 && buf[3] == 0x4F &&
buf[4] == 0x00
}

View File

@ -1,143 +0,0 @@
package matchers
import "github.com/h2non/filetype/matchers/isobmff"
var (
TypeJpeg = newType("jpg", "image/jpeg")
TypeJpeg2000 = newType("jp2", "image/jp2")
TypePng = newType("png", "image/png")
TypeGif = newType("gif", "image/gif")
TypeWebp = newType("webp", "image/webp")
TypeCR2 = newType("cr2", "image/x-canon-cr2")
TypeTiff = newType("tif", "image/tiff")
TypeBmp = newType("bmp", "image/bmp")
TypeJxr = newType("jxr", "image/vnd.ms-photo")
TypePsd = newType("psd", "image/vnd.adobe.photoshop")
TypeIco = newType("ico", "image/vnd.microsoft.icon")
TypeHeif = newType("heif", "image/heif")
TypeDwg = newType("dwg", "image/vnd.dwg")
)
var Image = Map{
TypeJpeg: Jpeg,
TypeJpeg2000: Jpeg2000,
TypePng: Png,
TypeGif: Gif,
TypeWebp: Webp,
TypeCR2: CR2,
TypeTiff: Tiff,
TypeBmp: Bmp,
TypeJxr: Jxr,
TypePsd: Psd,
TypeIco: Ico,
TypeHeif: Heif,
TypeDwg: Dwg,
}
func Jpeg(buf []byte) bool {
return len(buf) > 2 &&
buf[0] == 0xFF &&
buf[1] == 0xD8 &&
buf[2] == 0xFF
}
func Jpeg2000(buf []byte) bool {
return len(buf) > 12 &&
buf[0] == 0x0 &&
buf[1] == 0x0 &&
buf[2] == 0x0 &&
buf[3] == 0xC &&
buf[4] == 0x6A &&
buf[5] == 0x50 &&
buf[6] == 0x20 &&
buf[7] == 0x20 &&
buf[8] == 0xD &&
buf[9] == 0xA &&
buf[10] == 0x87 &&
buf[11] == 0xA &&
buf[12] == 0x0
}
func Png(buf []byte) bool {
return len(buf) > 3 &&
buf[0] == 0x89 && buf[1] == 0x50 &&
buf[2] == 0x4E && buf[3] == 0x47
}
func Gif(buf []byte) bool {
return len(buf) > 2 &&
buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46
}
func Webp(buf []byte) bool {
return len(buf) > 11 &&
buf[8] == 0x57 && buf[9] == 0x45 &&
buf[10] == 0x42 && buf[11] == 0x50
}
func CR2(buf []byte) bool {
return len(buf) > 10 &&
((buf[0] == 0x49 && buf[1] == 0x49 && buf[2] == 0x2A && buf[3] == 0x0) || // Little Endian
(buf[0] == 0x4D && buf[1] == 0x4D && buf[2] == 0x0 && buf[3] == 0x2A)) && // Big Endian
buf[8] == 0x43 && buf[9] == 0x52 && // CR2 magic word
buf[10] == 0x02 // CR2 major version
}
func Tiff(buf []byte) bool {
return len(buf) > 10 &&
((buf[0] == 0x49 && buf[1] == 0x49 && buf[2] == 0x2A && buf[3] == 0x0) || // Little Endian
(buf[0] == 0x4D && buf[1] == 0x4D && buf[2] == 0x0 && buf[3] == 0x2A)) && // Big Endian
!CR2(buf) // To avoid conflicts differentiate Tiff from CR2
}
func Bmp(buf []byte) bool {
return len(buf) > 1 &&
buf[0] == 0x42 &&
buf[1] == 0x4D
}
func Jxr(buf []byte) bool {
return len(buf) > 2 &&
buf[0] == 0x49 &&
buf[1] == 0x49 &&
buf[2] == 0xBC
}
func Psd(buf []byte) bool {
return len(buf) > 3 &&
buf[0] == 0x38 && buf[1] == 0x42 &&
buf[2] == 0x50 && buf[3] == 0x53
}
func Ico(buf []byte) bool {
return len(buf) > 3 &&
buf[0] == 0x00 && buf[1] == 0x00 &&
buf[2] == 0x01 && buf[3] == 0x00
}
func Heif(buf []byte) bool {
if !isobmff.IsISOBMFF(buf) {
return false
}
majorBrand, _, compatibleBrands := isobmff.GetFtyp(buf)
if majorBrand == "heic" {
return true
}
if majorBrand == "mif1" || majorBrand == "msf1" {
for _, compatibleBrand := range compatibleBrands {
if compatibleBrand == "heic" {
return true
}
}
}
return false
}
func Dwg(buf []byte) bool {
return len(buf) > 3 &&
buf[0] == 0x41 && buf[1] == 0x43 &&
buf[2] == 0x31 && buf[3] == 0x30
}

View File

@ -1,37 +0,0 @@
package isobmff
import "encoding/binary"
// IsISOBMFF checks whether the given buffer represents ISO Base Media File Format data
func IsISOBMFF(buf []byte) bool {
if len(buf) < 16 || string(buf[4:8]) != "ftyp" {
return false
}
if ftypLength := binary.BigEndian.Uint32(buf[0:4]); len(buf) < int(ftypLength) {
return false
}
return true
}
// GetFtyp returns the major brand, minor version and compatible brands of the ISO-BMFF data
func GetFtyp(buf []byte) (string, string, []string) {
if len(buf) < 17 {
return "", "", []string{""}
}
ftypLength := binary.BigEndian.Uint32(buf[0:4])
majorBrand := string(buf[8:12])
minorVersion := string(buf[12:16])
compatibleBrands := []string{}
for i := 16; i < int(ftypLength); i += 4 {
if len(buf) >= (i + 4) {
compatibleBrands = append(compatibleBrands, string(buf[i:i+4]))
}
}
return majorBrand, minorVersion, compatibleBrands
}

View File

@ -1,51 +0,0 @@
package matchers
import (
"github.com/h2non/filetype/types"
)
// Internal shortcut to NewType
var newType = types.NewType
// Matcher function interface as type alias
type Matcher func([]byte) bool
// Type interface to store pairs of type with its matcher function
type Map map[types.Type]Matcher
// Type specific matcher function interface
type TypeMatcher func([]byte) types.Type
// Store registered file type matchers
var Matchers = make(map[types.Type]TypeMatcher)
var MatcherKeys []types.Type
// Create and register a new type matcher function
func NewMatcher(kind types.Type, fn Matcher) TypeMatcher {
matcher := func(buf []byte) types.Type {
if fn(buf) {
return kind
}
return types.Unknown
}
Matchers[kind] = matcher
// prepend here so any user defined matchers get added first
MatcherKeys = append([]types.Type{kind}, MatcherKeys...)
return matcher
}
func register(matchers ...Map) {
MatcherKeys = MatcherKeys[:0]
for _, m := range matchers {
for kind, matcher := range m {
NewMatcher(kind, matcher)
}
}
}
func init() {
// Arguments order is intentional
// Archive files will be checked last due to prepend above in func NewMatcher
register(Archive, Document, Font, Audio, Video, Image, Application)
}

View File

@ -1,145 +0,0 @@
package matchers
import "bytes"
var (
TypeMp4 = newType("mp4", "video/mp4")
TypeM4v = newType("m4v", "video/x-m4v")
TypeMkv = newType("mkv", "video/x-matroska")
TypeWebm = newType("webm", "video/webm")
TypeMov = newType("mov", "video/quicktime")
TypeAvi = newType("avi", "video/x-msvideo")
TypeWmv = newType("wmv", "video/x-ms-wmv")
TypeMpeg = newType("mpg", "video/mpeg")
TypeFlv = newType("flv", "video/x-flv")
Type3gp = newType("3gp", "video/3gpp")
)
var Video = Map{
TypeMp4: Mp4,
TypeM4v: M4v,
TypeMkv: Mkv,
TypeWebm: Webm,
TypeMov: Mov,
TypeAvi: Avi,
TypeWmv: Wmv,
TypeMpeg: Mpeg,
TypeFlv: Flv,
Type3gp: Match3gp,
}
func M4v(buf []byte) bool {
return len(buf) > 10 &&
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) bool {
return len(buf) > 3 &&
buf[0] == 0x1A && buf[1] == 0x45 &&
buf[2] == 0xDF && buf[3] == 0xA3 &&
containsMatroskaSignature(buf, []byte{'m', 'a', 't', 'r', 'o', 's', 'k', 'a'})
}
func Webm(buf []byte) bool {
return len(buf) > 3 &&
buf[0] == 0x1A && buf[1] == 0x45 &&
buf[2] == 0xDF && buf[3] == 0xA3 &&
containsMatroskaSignature(buf, []byte{'w', 'e', 'b', 'm'})
}
func Mov(buf []byte) bool {
return len(buf) > 15 && ((buf[0] == 0x0 && buf[1] == 0x0 &&
buf[2] == 0x0 && buf[3] == 0x14 &&
buf[4] == 0x66 && buf[5] == 0x74 &&
buf[6] == 0x79 && buf[7] == 0x70) ||
(buf[4] == 0x6d && buf[5] == 0x6f && buf[6] == 0x6f && buf[7] == 0x76) ||
(buf[4] == 0x6d && buf[5] == 0x64 && buf[6] == 0x61 && buf[7] == 0x74) ||
(buf[12] == 0x6d && buf[13] == 0x64 && buf[14] == 0x61 && buf[15] == 0x74))
}
func Avi(buf []byte) bool {
return len(buf) > 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) bool {
return len(buf) > 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) bool {
return len(buf) > 3 &&
buf[0] == 0x0 && buf[1] == 0x0 &&
buf[2] == 0x1 && buf[3] >= 0xb0 &&
buf[3] <= 0xbf
}
func Flv(buf []byte) bool {
return len(buf) > 3 &&
buf[0] == 0x46 && buf[1] == 0x4C &&
buf[2] == 0x56 && buf[3] == 0x01
}
func Mp4(buf []byte) bool {
return len(buf) > 11 &&
(buf[4] == 'f' && buf[5] == 't' && buf[6] == 'y' && buf[7] == 'p') &&
((buf[8] == 'a' && buf[9] == 'v' && buf[10] == 'c' && buf[11] == '1') ||
(buf[8] == 'd' && buf[9] == 'a' && buf[10] == 's' && buf[11] == 'h') ||
(buf[8] == 'i' && buf[9] == 's' && buf[10] == 'o' && buf[11] == '2') ||
(buf[8] == 'i' && buf[9] == 's' && buf[10] == 'o' && buf[11] == '3') ||
(buf[8] == 'i' && buf[9] == 's' && buf[10] == 'o' && buf[11] == '4') ||
(buf[8] == 'i' && buf[9] == 's' && buf[10] == 'o' && buf[11] == '5') ||
(buf[8] == 'i' && buf[9] == 's' && buf[10] == 'o' && buf[11] == '6') ||
(buf[8] == 'i' && buf[9] == 's' && buf[10] == 'o' && buf[11] == 'm') ||
(buf[8] == 'm' && buf[9] == 'm' && buf[10] == 'p' && buf[11] == '4') ||
(buf[8] == 'm' && buf[9] == 'p' && buf[10] == '4' && buf[11] == '1') ||
(buf[8] == 'm' && buf[9] == 'p' && buf[10] == '4' && buf[11] == '2') ||
(buf[8] == 'm' && buf[9] == 'p' && buf[10] == '4' && buf[11] == 'v') ||
(buf[8] == 'm' && buf[9] == 'p' && buf[10] == '7' && buf[11] == '1') ||
(buf[8] == 'M' && buf[9] == 'S' && buf[10] == 'N' && buf[11] == 'V') ||
(buf[8] == 'N' && buf[9] == 'D' && buf[10] == 'A' && buf[11] == 'S') ||
(buf[8] == 'N' && buf[9] == 'D' && buf[10] == 'S' && buf[11] == 'C') ||
(buf[8] == 'N' && buf[9] == 'S' && buf[10] == 'D' && buf[11] == 'C') ||
(buf[8] == 'N' && buf[9] == 'D' && buf[10] == 'S' && buf[11] == 'H') ||
(buf[8] == 'N' && buf[9] == 'D' && buf[10] == 'S' && buf[11] == 'M') ||
(buf[8] == 'N' && buf[9] == 'D' && buf[10] == 'S' && buf[11] == 'P') ||
(buf[8] == 'N' && buf[9] == 'D' && buf[10] == 'S' && buf[11] == 'S') ||
(buf[8] == 'N' && buf[9] == 'D' && buf[10] == 'X' && buf[11] == 'C') ||
(buf[8] == 'N' && buf[9] == 'D' && buf[10] == 'X' && buf[11] == 'H') ||
(buf[8] == 'N' && buf[9] == 'D' && buf[10] == 'X' && buf[11] == 'M') ||
(buf[8] == 'N' && buf[9] == 'D' && buf[10] == 'X' && buf[11] == 'P') ||
(buf[8] == 'N' && buf[9] == 'D' && buf[10] == 'X' && buf[11] == 'S') ||
(buf[8] == 'F' && buf[9] == '4' && buf[10] == 'V' && buf[11] == ' ') ||
(buf[8] == 'F' && buf[9] == '4' && buf[10] == 'P' && buf[11] == ' '))
}
func Match3gp(buf []byte) bool {
return len(buf) > 10 &&
buf[4] == 0x66 && buf[5] == 0x74 && buf[6] == 0x79 &&
buf[7] == 0x70 && buf[8] == 0x33 && buf[9] == 0x67 &&
buf[10] == 0x70
}
func containsMatroskaSignature(buf, subType []byte) bool {
limit := 4096
if len(buf) < limit {
limit = len(buf)
}
index := bytes.Index(buf[:limit], subType)
if index < 3 {
return false
}
return buf[index-3] == 0x42 && buf[index-2] == 0x82
}

View File

@ -1,4 +0,0 @@
package types
// Unknown default type
var Unknown = NewType("unknown", "")

View File

@ -1,14 +0,0 @@
package types
// MIME stores the file MIME type values
type MIME struct {
Type string
Subtype string
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,11 +0,0 @@
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], ""
}

View File

@ -1,16 +0,0 @@
package types
// Type represents a file MIME type and its extension
type Type struct {
MIME MIME
Extension string
}
// NewType creates a new Type
func NewType(ext, mime string) Type {
t := Type{
MIME: NewMIME(mime),
Extension: ext,
}
return Add(t)
}

View File

@ -1,23 +0,0 @@
package types
import "sync"
// Types Support concurrent map writes
var Types sync.Map
// Add registers a new type in the package
func Add(t Type) Type {
Types.Store(t.Extension, t)
return t
}
// Get retrieves a Type by extension
func Get(ext string) Type {
if tmp, ok := Types.Load(ext); ok {
kind := tmp.(Type)
if kind.Extension != "" {
return kind
}
}
return Unknown
}

View File

@ -1,4 +0,0 @@
package filetype
// Version exposes the current package version.
const Version = "1.1.3"

6
vendor/modules.txt vendored
View File

@ -1,9 +1,3 @@
# github.com/h2non/filetype v1.1.3
## explicit; go 1.13
github.com/h2non/filetype
github.com/h2non/filetype/matchers
github.com/h2non/filetype/matchers/isobmff
github.com/h2non/filetype/types
# github.com/inconshreveable/mousetrap v1.1.0 # github.com/inconshreveable/mousetrap v1.1.0
## explicit; go 1.18 ## explicit; go 1.18
github.com/inconshreveable/mousetrap github.com/inconshreveable/mousetrap