diff --git a/Gopkg.lock b/Gopkg.lock
index 72bf7ff6b..0e8dfe6a5 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -100,6 +100,18 @@
pruneopts = "UT"
revision = "b57537c92a6b4155c5219ce0d7d2a9c0cc74b1c4"
+[[projects]]
+ digest = "1:04904b7a6f24221e7999b554094e347885b4837beeba9933efadc93f4f5421ff"
+ name = "github.com/gabriel-vasile/mimetype"
+ packages = [
+ ".",
+ "internal/json",
+ "internal/matchers",
+ ]
+ pruneopts = "UT"
+ revision = "b9686d36d26efccccd34eff91a19f42a61bcdc95"
+ version = "v0.3.17"
+
[[projects]]
digest = "1:f59f34bb582fbc13885cd0338570bc0f21738fc711065527c36928213de62c1b"
name = "github.com/getsentry/sentry-go"
@@ -690,6 +702,7 @@
"github.com/dgrijalva/jwt-go",
"github.com/disintegration/imaging",
"github.com/edwvee/exiffix",
+ "github.com/gabriel-vasile/mimetype",
"github.com/getsentry/sentry-go",
"github.com/getsentry/sentry-go/http",
"github.com/go-chi/chi",
diff --git a/Gopkg.toml b/Gopkg.toml
index 244d52ecf..c751eac86 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -29,3 +29,7 @@ required = [
[[prune.project]]
name = "github.com/cortezaproject/corteza-protobuf"
unused-packages = false
+
+[[constraint]]
+ name = "github.com/gabriel-vasile/mimetype"
+ version = "0.3.17"
diff --git a/vendor/github.com/gabriel-vasile/mimetype/.gitattributes b/vendor/github.com/gabriel-vasile/mimetype/.gitattributes
new file mode 100644
index 000000000..0cc26ec01
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/.gitattributes
@@ -0,0 +1 @@
+testdata/* linguist-vendored
diff --git a/vendor/github.com/gabriel-vasile/mimetype/.travis.yml b/vendor/github.com/gabriel-vasile/mimetype/.travis.yml
new file mode 100644
index 000000000..9f453191c
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/.travis.yml
@@ -0,0 +1,14 @@
+language: go
+go:
+ - "1.9"
+ - "1.10"
+before_install:
+ - go get github.com/mattn/goveralls
+ - go get github.com/client9/misspell/cmd/misspell
+before_script:
+ - go tool vet .
+script:
+ - diff -u <(echo -n) <(gofmt -d ./)
+ - go test -v
+ - $GOPATH/bin/goveralls -service=travis-ci
+ - misspell -locale US -error *.md *.go
diff --git a/vendor/github.com/gabriel-vasile/mimetype/CODE_OF_CONDUCT.md b/vendor/github.com/gabriel-vasile/mimetype/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..8479cd87d
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/CODE_OF_CONDUCT.md
@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at vasile.gabriel@email.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/vendor/github.com/gabriel-vasile/mimetype/CONTRIBUTING.md b/vendor/github.com/gabriel-vasile/mimetype/CONTRIBUTING.md
new file mode 100644
index 000000000..56ae4e57c
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/CONTRIBUTING.md
@@ -0,0 +1,12 @@
+## Contribute
+Contributions to **mimetype** are welcome. If you find an issue and you consider
+contributing, you can use the [Github issues tracker](https://github.com/gabriel-vasile/mimetype/issues)
+in order to report it, or better yet, open a pull request.
+
+Code contributions must respect these rules:
+ - code must be test covered
+ - code must be formatted using gofmt tool
+ - exported names must be documented
+
+**Important**: By submitting a pull request, you agree to allow the project
+owner to license your work under the same license as that used by the project.
diff --git a/vendor/github.com/gabriel-vasile/mimetype/LICENSE b/vendor/github.com/gabriel-vasile/mimetype/LICENSE
new file mode 100644
index 000000000..f1b456e91
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018, 2019 Gabriel Vasile
+
+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/vendor/github.com/gabriel-vasile/mimetype/README.md b/vendor/github.com/gabriel-vasile/mimetype/README.md
new file mode 100644
index 000000000..3d7f4afc5
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/README.md
@@ -0,0 +1,64 @@
+
+ mimetype
+
+
+
+ A package for detecting MIME types and extensions based on magic numbers
+
+
+ No bindings, all written in pure go
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Install
+```bash
+go get github.com/gabriel-vasile/mimetype
+```
+
+## Use
+The library exposes three functions you can use in order to detect a file type.
+See [Godoc](https://godoc.org/github.com/gabriel-vasile/mimetype) for full reference.
+```go
+func Detect(in []byte) (mime, extension string) {...}
+func DetectReader(r io.Reader) (mime, extension string, err error) {...}
+func DetectFile(file string) (mime, extension string, err error) {...}
+```
+When detecting from a `ReadSeeker` interface, such as `os.File`, make sure
+to reset the offset of the reader to the beginning if needed:
+```go
+_, err = file.Seek(0, io.SeekStart)
+```
+
+## Supported MIME types
+See [supported mimes](supported_mimes.md) for the list of detected MIME types.
+If support is needed for a specific file format, please open an [issue](https://github.com/gabriel-vasile/mimetype/issues/new/choose).
+
+## Structure
+**mimetype** uses an hierarchical structure to keep the matching functions.
+This reduces the number of calls needed for detecting the file type. The reason
+behind this choice is that there are file formats used as containers for other
+file formats. For example, Microsoft office files are just zip archives,
+containing specific metadata files.
+
+

+
+
+## Contributing
+See [CONTRIBUTING.md](CONTRIBUTING.md).
diff --git a/vendor/github.com/gabriel-vasile/mimetype/go.mod b/vendor/github.com/gabriel-vasile/mimetype/go.mod
new file mode 100644
index 000000000..6f8542d53
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/go.mod
@@ -0,0 +1 @@
+module github.com/gabriel-vasile/mimetype
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/json/json.go b/vendor/github.com/gabriel-vasile/mimetype/internal/json/json.go
new file mode 100644
index 000000000..9aef6cb4c
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/json/json.go
@@ -0,0 +1,536 @@
+// Copyright (c) 2009 The Go Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// JSON value parser state machine.
+// This package is almost entirely copied from the Go stdlib.
+// Changes made to it permit users of the package to tell
+// if some slice of bytes is a valid beginning of a json string.
+package json
+
+import "fmt"
+
+type (
+ context int
+ scanStatus int
+)
+
+const (
+ contextKey context = iota
+ contextObj
+ contextArr
+
+ scanContinue scanStatus = iota // uninteresting byte
+ scanBeginLiteral // end implied by next result != scanContinue
+ scanBeginObject // begin object
+ scanObjectKey // just finished object key (string)
+ scanObjectValue // just finished non-last object value
+ scanEndObject // end object (implies scanObjectValue if possible)
+ scanBeginArray // begin array
+ scanArrayValue // just finished array value
+ scanEndArray // end array (implies scanArrayValue if possible)
+ scanSkipSpace // space byte; can skip; known to be last "continue" result
+ scanEnd // top-level value ended *before* this byte; known to be first "stop" result
+ scanError // hit an error, scanner.err.
+)
+
+type (
+ scanner struct {
+ step func(*scanner, byte) scanStatus
+ contexts []context
+ endTop bool
+ err error
+ index int
+ }
+)
+
+// Scan returns the number of bytes scanned and if there was any error
+// in trying to reach the end of data
+func Scan(data []byte) (int, error) {
+ s := &scanner{}
+ _ = checkValid(data, s)
+ return s.index, s.err
+}
+
+// checkValid verifies that data is valid JSON-encoded data.
+// scan is passed in for use by checkValid to avoid an allocation.
+func checkValid(data []byte, scan *scanner) error {
+ scan.reset()
+ for _, c := range data {
+ scan.index++
+ if scan.step(scan, c) == scanError {
+ return scan.err
+ }
+ }
+ if scan.eof() == scanError {
+ return scan.err
+ }
+ return nil
+}
+
+func isSpace(c byte) bool {
+ return c == ' ' || c == '\t' || c == '\r' || c == '\n'
+}
+
+func (s *scanner) reset() {
+ s.step = stateBeginValue
+ s.contexts = s.contexts[0:0]
+ s.err = nil
+}
+
+// eof tells the scanner that the end of input has been reached.
+// It returns a scan status just as s.step does.
+func (s *scanner) eof() scanStatus {
+ if s.err != nil {
+ return scanError
+ }
+ if s.endTop {
+ return scanEnd
+ }
+ s.step(s, ' ')
+ if s.endTop {
+ return scanEnd
+ }
+ if s.err == nil {
+ s.err = fmt.Errorf("unexpected end of JSON input")
+ }
+ return scanError
+}
+
+// pushContext pushes a new parse state p onto the parse stack.
+func (s *scanner) pushParseState(p context) {
+ s.contexts = append(s.contexts, p)
+}
+
+// popParseState pops a parse state (already obtained) off the stack
+// and updates s.step accordingly.
+func (s *scanner) popParseState() {
+ n := len(s.contexts) - 1
+ s.contexts = s.contexts[0:n]
+ if n == 0 {
+ s.step = stateEndTop
+ s.endTop = true
+ } else {
+ s.step = stateEndValue
+ }
+}
+
+// stateBeginValueOrEmpty is the state after reading `[`.
+func stateBeginValueOrEmpty(s *scanner, c byte) scanStatus {
+ if c <= ' ' && isSpace(c) {
+ return scanSkipSpace
+ }
+ if c == ']' {
+ return stateEndValue(s, c)
+ }
+ return stateBeginValue(s, c)
+}
+
+// stateBeginValue is the state at the beginning of the input.
+func stateBeginValue(s *scanner, c byte) scanStatus {
+ if c <= ' ' && isSpace(c) {
+ return scanSkipSpace
+ }
+ switch c {
+ case '{':
+ s.step = stateBeginStringOrEmpty
+ s.pushParseState(contextKey)
+ return scanBeginObject
+ case '[':
+ s.step = stateBeginValueOrEmpty
+ s.pushParseState(contextArr)
+ return scanBeginArray
+ case '"':
+ s.step = stateInString
+ return scanBeginLiteral
+ case '-':
+ s.step = stateNeg
+ return scanBeginLiteral
+ case '0': // beginning of 0.123
+ s.step = state0
+ return scanBeginLiteral
+ case 't': // beginning of true
+ s.step = stateT
+ return scanBeginLiteral
+ case 'f': // beginning of false
+ s.step = stateF
+ return scanBeginLiteral
+ case 'n': // beginning of null
+ s.step = stateN
+ return scanBeginLiteral
+ }
+ if '1' <= c && c <= '9' { // beginning of 1234.5
+ s.step = state1
+ return scanBeginLiteral
+ }
+ return s.error(c, "looking for beginning of value")
+}
+
+// stateBeginStringOrEmpty is the state after reading `{`.
+func stateBeginStringOrEmpty(s *scanner, c byte) scanStatus {
+ if c <= ' ' && isSpace(c) {
+ return scanSkipSpace
+ }
+ if c == '}' {
+ n := len(s.contexts)
+ s.contexts[n-1] = contextObj
+ return stateEndValue(s, c)
+ }
+ return stateBeginString(s, c)
+}
+
+// stateBeginString is the state after reading `{"key": value,`.
+func stateBeginString(s *scanner, c byte) scanStatus {
+ if c <= ' ' && isSpace(c) {
+ return scanSkipSpace
+ }
+ if c == '"' {
+ s.step = stateInString
+ return scanBeginLiteral
+ }
+ return s.error(c, "looking for beginning of object key string")
+}
+
+// stateEndValue is the state after completing a value,
+// such as after reading `{}` or `true` or `["x"`.
+func stateEndValue(s *scanner, c byte) scanStatus {
+ n := len(s.contexts)
+ if n == 0 {
+ // Completed top-level before the current byte.
+ s.step = stateEndTop
+ s.endTop = true
+ return stateEndTop(s, c)
+ }
+ if c <= ' ' && isSpace(c) {
+ s.step = stateEndValue
+ return scanSkipSpace
+ }
+ ps := s.contexts[n-1]
+ switch ps {
+ case contextKey:
+ if c == ':' {
+ s.contexts[n-1] = contextObj
+ s.step = stateBeginValue
+ return scanObjectKey
+ }
+ return s.error(c, "after object key")
+ case contextObj:
+ if c == ',' {
+ s.contexts[n-1] = contextKey
+ s.step = stateBeginString
+ return scanObjectValue
+ }
+ if c == '}' {
+ s.popParseState()
+ return scanEndObject
+ }
+ return s.error(c, "after object key:value pair")
+ case contextArr:
+ if c == ',' {
+ s.step = stateBeginValue
+ return scanArrayValue
+ }
+ if c == ']' {
+ s.popParseState()
+ return scanEndArray
+ }
+ return s.error(c, "after array element")
+ }
+ return s.error(c, "")
+}
+
+// stateEndTop is the state after finishing the top-level value,
+// such as after reading `{}` or `[1,2,3]`.
+// Only space characters should be seen now.
+func stateEndTop(s *scanner, c byte) scanStatus {
+ if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
+ // Complain about non-space byte on next call.
+ s.error(c, "after top-level value")
+ }
+ return scanEnd
+}
+
+// stateInString is the state after reading `"`.
+func stateInString(s *scanner, c byte) scanStatus {
+ if c == '"' {
+ s.step = stateEndValue
+ return scanContinue
+ }
+ if c == '\\' {
+ s.step = stateInStringEsc
+ return scanContinue
+ }
+ if c < 0x20 {
+ return s.error(c, "in string literal")
+ }
+ return scanContinue
+}
+
+// stateInStringEsc is the state after reading `"\` during a quoted string.
+func stateInStringEsc(s *scanner, c byte) scanStatus {
+ switch c {
+ case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
+ s.step = stateInString
+ return scanContinue
+ case 'u':
+ s.step = stateInStringEscU
+ return scanContinue
+ }
+ return s.error(c, "in string escape code")
+}
+
+// stateInStringEscU is the state after reading `"\u` during a quoted string.
+func stateInStringEscU(s *scanner, c byte) scanStatus {
+ if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
+ s.step = stateInStringEscU1
+ return scanContinue
+ }
+ // numbers
+ return s.error(c, "in \\u hexadecimal character escape")
+}
+
+// stateInStringEscU1 is the state after reading `"\u1` during a quoted string.
+func stateInStringEscU1(s *scanner, c byte) scanStatus {
+ if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
+ s.step = stateInStringEscU12
+ return scanContinue
+ }
+ // numbers
+ return s.error(c, "in \\u hexadecimal character escape")
+}
+
+// stateInStringEscU12 is the state after reading `"\u12` during a quoted string.
+func stateInStringEscU12(s *scanner, c byte) scanStatus {
+ if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
+ s.step = stateInStringEscU123
+ return scanContinue
+ }
+ // numbers
+ return s.error(c, "in \\u hexadecimal character escape")
+}
+
+// stateInStringEscU123 is the state after reading `"\u123` during a quoted string.
+func stateInStringEscU123(s *scanner, c byte) scanStatus {
+ if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
+ s.step = stateInString
+ return scanContinue
+ }
+ // numbers
+ return s.error(c, "in \\u hexadecimal character escape")
+}
+
+// stateNeg is the state after reading `-` during a number.
+func stateNeg(s *scanner, c byte) scanStatus {
+ if c == '0' {
+ s.step = state0
+ return scanContinue
+ }
+ if '1' <= c && c <= '9' {
+ s.step = state1
+ return scanContinue
+ }
+ return s.error(c, "in numeric literal")
+}
+
+// state1 is the state after reading a non-zero integer during a number,
+// such as after reading `1` or `100` but not `0`.
+func state1(s *scanner, c byte) scanStatus {
+ if '0' <= c && c <= '9' {
+ s.step = state1
+ return scanContinue
+ }
+ return state0(s, c)
+}
+
+// state0 is the state after reading `0` during a number.
+func state0(s *scanner, c byte) scanStatus {
+ if c == '.' {
+ s.step = stateDot
+ return scanContinue
+ }
+ if c == 'e' || c == 'E' {
+ s.step = stateE
+ return scanContinue
+ }
+ return stateEndValue(s, c)
+}
+
+// stateDot is the state after reading the integer and decimal point in a number,
+// such as after reading `1.`.
+func stateDot(s *scanner, c byte) scanStatus {
+ if '0' <= c && c <= '9' {
+ s.step = stateDot0
+ return scanContinue
+ }
+ return s.error(c, "after decimal point in numeric literal")
+}
+
+// stateDot0 is the state after reading the integer, decimal point, and subsequent
+// digits of a number, such as after reading `3.14`.
+func stateDot0(s *scanner, c byte) scanStatus {
+ if '0' <= c && c <= '9' {
+ return scanContinue
+ }
+ if c == 'e' || c == 'E' {
+ s.step = stateE
+ return scanContinue
+ }
+ return stateEndValue(s, c)
+}
+
+// stateE is the state after reading the mantissa and e in a number,
+// such as after reading `314e` or `0.314e`.
+func stateE(s *scanner, c byte) scanStatus {
+ if c == '+' || c == '-' {
+ s.step = stateESign
+ return scanContinue
+ }
+ return stateESign(s, c)
+}
+
+// stateESign is the state after reading the mantissa, e, and sign in a number,
+// such as after reading `314e-` or `0.314e+`.
+func stateESign(s *scanner, c byte) scanStatus {
+ if '0' <= c && c <= '9' {
+ s.step = stateE0
+ return scanContinue
+ }
+ return s.error(c, "in exponent of numeric literal")
+}
+
+// stateE0 is the state after reading the mantissa, e, optional sign,
+// and at least one digit of the exponent in a number,
+// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
+func stateE0(s *scanner, c byte) scanStatus {
+ if '0' <= c && c <= '9' {
+ return scanContinue
+ }
+ return stateEndValue(s, c)
+}
+
+// stateT is the state after reading `t`.
+func stateT(s *scanner, c byte) scanStatus {
+ if c == 'r' {
+ s.step = stateTr
+ return scanContinue
+ }
+ return s.error(c, "in literal true (expecting 'r')")
+}
+
+// stateTr is the state after reading `tr`.
+func stateTr(s *scanner, c byte) scanStatus {
+ if c == 'u' {
+ s.step = stateTru
+ return scanContinue
+ }
+ return s.error(c, "in literal true (expecting 'u')")
+}
+
+// stateTru is the state after reading `tru`.
+func stateTru(s *scanner, c byte) scanStatus {
+ if c == 'e' {
+ s.step = stateEndValue
+ return scanContinue
+ }
+ return s.error(c, "in literal true (expecting 'e')")
+}
+
+// stateF is the state after reading `f`.
+func stateF(s *scanner, c byte) scanStatus {
+ if c == 'a' {
+ s.step = stateFa
+ return scanContinue
+ }
+ return s.error(c, "in literal false (expecting 'a')")
+}
+
+// stateFa is the state after reading `fa`.
+func stateFa(s *scanner, c byte) scanStatus {
+ if c == 'l' {
+ s.step = stateFal
+ return scanContinue
+ }
+ return s.error(c, "in literal false (expecting 'l')")
+}
+
+// stateFal is the state after reading `fal`.
+func stateFal(s *scanner, c byte) scanStatus {
+ if c == 's' {
+ s.step = stateFals
+ return scanContinue
+ }
+ return s.error(c, "in literal false (expecting 's')")
+}
+
+// stateFals is the state after reading `fals`.
+func stateFals(s *scanner, c byte) scanStatus {
+ if c == 'e' {
+ s.step = stateEndValue
+ return scanContinue
+ }
+ return s.error(c, "in literal false (expecting 'e')")
+}
+
+// stateN is the state after reading `n`.
+func stateN(s *scanner, c byte) scanStatus {
+ if c == 'u' {
+ s.step = stateNu
+ return scanContinue
+ }
+ return s.error(c, "in literal null (expecting 'u')")
+}
+
+// stateNu is the state after reading `nu`.
+func stateNu(s *scanner, c byte) scanStatus {
+ if c == 'l' {
+ s.step = stateNul
+ return scanContinue
+ }
+ return s.error(c, "in literal null (expecting 'l')")
+}
+
+// stateNul is the state after reading `nul`.
+func stateNul(s *scanner, c byte) scanStatus {
+ if c == 'l' {
+ s.step = stateEndValue
+ return scanContinue
+ }
+ return s.error(c, "in literal null (expecting 'l')")
+}
+
+// stateError is the state after reaching a syntax error,
+// such as after reading `[1}` or `5.1.2`.
+func stateError(s *scanner, c byte) scanStatus {
+ return scanError
+}
+
+// error records an error and switches to the error state.
+func (s *scanner) error(c byte, context string) scanStatus {
+ s.step = stateError
+ s.err = fmt.Errorf("invalid character <<%c>> %s", c, context)
+ return scanError
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/archive.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/archive.go
new file mode 100644
index 000000000..db3b2150f
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/archive.go
@@ -0,0 +1,78 @@
+package matchers
+
+import "bytes"
+
+// Zip matches a zip archive.
+func Zip(in []byte) bool {
+ return len(in) > 3 &&
+ in[0] == 0x50 && in[1] == 0x4B &&
+ (in[2] == 0x3 || in[2] == 0x5 || in[2] == 0x7) &&
+ (in[3] == 0x4 || in[3] == 0x6 || in[3] == 0x8)
+}
+
+// SevenZ matches a 7z archive.
+func SevenZ(in []byte) bool {
+ return len(in) > 6 &&
+ bytes.Equal(in[:6], []byte{0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C})
+}
+
+// Epub matches an EPUB file.
+func Epub(in []byte) bool {
+ return len(in) > 58 && bytes.Equal(in[30:58], []byte("mimetypeapplication/epub+zip"))
+}
+
+// Jar matches a Java archive file.
+func Jar(in []byte) bool {
+ return bytes.Contains(in, []byte("META-INF/MANIFEST.MF"))
+}
+
+// Gzip matched gzip files based on http://www.zlib.org/rfc-gzip.html#header-trailer.
+func Gzip(in []byte) bool {
+ return len(in) > 2 && bytes.Equal(in[:2], []byte{0x1f, 0x8b})
+}
+
+// Crx matches a Chrome extension file: a zip archive prepended by "Cr24".
+func Crx(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("Cr24"))
+}
+
+// Tar matches a (t)ape (ar)chive file.
+func Tar(in []byte) bool {
+ return len(in) > 262 && bytes.Equal(in[257:262], []byte("ustar"))
+}
+
+// Fits matches an Flexible Image Transport System file.
+func Fits(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x53, 0x49, 0x4D, 0x50, 0x4C, 0x45, 0x20,
+ 0x20, 0x3D, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54})
+}
+
+// Xar matches an eXtensible ARchive format file.
+func Xar(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x78, 0x61, 0x72, 0x21})
+}
+
+// Bz2 matches a bzip2 file.
+func Bz2(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x42, 0x5A, 0x68})
+}
+
+// Ar matches an ar (Unix) archive file.
+func Ar(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E})
+}
+
+// Deb matches a Debian package file
+func Deb(in []byte) bool {
+ return len(in) > 8 && bytes.HasPrefix(in[8:], []byte{0x64, 0x65, 0x62, 0x69,
+ 0x61, 0x6E, 0x2D, 0x62, 0x69, 0x6E, 0x61, 0x72, 0x79})
+}
+
+// Rar matches a RAR archive file
+func Rar(in []byte) bool {
+ if !bytes.HasPrefix(in, []byte{0x52, 0x61, 0x72, 0x21, 0x1A, 0x07}) {
+ return false
+ }
+ return len(in) > 8 && (bytes.Equal(in[6:8], []byte{0x01, 0x00}) || in[6] == 0x00)
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/audio.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/audio.go
new file mode 100644
index 000000000..3b67a39e2
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/audio.go
@@ -0,0 +1,69 @@
+package matchers
+
+import (
+ "bytes"
+)
+
+// Mp3 matches an mp3 file.
+func Mp3(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("\x49\x44\x33"))
+}
+
+// Flac matches a Free Lossless Audio Codec file.
+func Flac(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("\x66\x4C\x61\x43\x00\x00\x00\x22"))
+}
+
+// Midi matches a Musical Instrument Digital Interface file.
+func Midi(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("\x4D\x54\x68\x64"))
+}
+
+// Ape matches a Monkey's Audio file.
+func Ape(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("\x4D\x41\x43\x20\x96\x0F\x00\x00\x34\x00\x00\x00\x18\x00\x00\x00\x90\xE3"))
+}
+
+// MusePack matches a Musepack file.
+func MusePack(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("MPCK"))
+}
+
+// Wav matches a Waveform Audio File Format file.
+func Wav(in []byte) bool {
+ return len(in) > 12 &&
+ bytes.Equal(in[:4], []byte("\x52\x49\x46\x46")) &&
+ bytes.Equal(in[8:12], []byte("\x57\x41\x56\x45"))
+}
+
+// Aiff matches Audio Interchange File Format file.
+func Aiff(in []byte) bool {
+ return len(in) > 12 &&
+ bytes.Equal(in[:4], []byte("\x46\x4F\x52\x4D")) &&
+ bytes.Equal(in[8:12], []byte("\x41\x49\x46\x46"))
+}
+
+// Ogg matches an Ogg file.
+func Ogg(in []byte) bool {
+ return len(in) > 5 && bytes.Equal(in[:5], []byte("\x4F\x67\x67\x53\x00"))
+}
+
+// Au matches a Sun Microsystems au file.
+func Au(in []byte) bool {
+ return len(in) > 4 && bytes.Equal(in[:4], []byte("\x2E\x73\x6E\x64"))
+}
+
+// Amr matches an Adaptive Multi-Rate file.
+func Amr(in []byte) bool {
+ return len(in) > 5 && bytes.Equal(in[:5], []byte("\x23\x21\x41\x4D\x52"))
+}
+
+// Aac matches an Advanced Audio Coding file.
+func Aac(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0xFF, 0xF1}) || bytes.HasPrefix(in, []byte{0xFF, 0xF9})
+}
+
+// Voc matches a Creative Voice file.
+func Voc(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("Creative Voice File"))
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/binary.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/binary.go
new file mode 100644
index 000000000..1f58346eb
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/binary.go
@@ -0,0 +1,88 @@
+package matchers
+
+import (
+ "bytes"
+)
+
+// Class matches an java class file.
+func Class(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0xCA, 0xFE, 0xBA, 0xBE})
+}
+
+// Swf matches an Adobe Flash swf file.
+func Swf(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("CWS")) ||
+ bytes.HasPrefix(in, []byte("FWS")) ||
+ bytes.HasPrefix(in, []byte("ZWS"))
+}
+
+// Wasm matches a web assembly File Format file.
+func Wasm(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x00, 0x61, 0x73, 0x6D})
+}
+
+// Dbf matches a dBase file.
+// https://www.dbase.com/Knowledgebase/INT/db7_file_fmt.htm
+func Dbf(in []byte) bool {
+ if len(in) < 4 {
+ return false
+ }
+
+ // 3rd and 4th bytes contain the last update month and day of month
+ if !(0 < in[2] && in[2] < 13 && 0 < in[3] && in[3] < 32) {
+ return false
+ }
+
+ // dbf type is dictated by the first byte
+ dbfTypes := []byte{
+ 0x02, 0x03, 0x04, 0x05, 0x30, 0x31, 0x32, 0x42, 0x62, 0x7B, 0x82,
+ 0x83, 0x87, 0x8A, 0x8B, 0x8E, 0xB3, 0xCB, 0xE5, 0xF5, 0xF4, 0xFB,
+ }
+ for _, b := range dbfTypes {
+ if in[0] == b {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Exe matches a Windows/DOS executable file.
+func Exe(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x4D, 0x5A})
+}
+
+// Elf matches an Executable and Linkable Format file.
+func Elf(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x7F, 0x45, 0x4C, 0x46})
+}
+
+// ElfObj matches an object file.
+func ElfObj(in []byte) bool {
+ return len(in) > 17 && ((in[16] == 0x01 && in[17] == 0x00) ||
+ (in[16] == 0x00 && in[17] == 0x01))
+}
+
+// ElfExe matches an executable file.
+func ElfExe(in []byte) bool {
+ return len(in) > 17 && ((in[16] == 0x02 && in[17] == 0x00) ||
+ (in[16] == 0x00 && in[17] == 0x02))
+}
+
+// ElfLib matches a shared library file.
+func ElfLib(in []byte) bool {
+ return len(in) > 17 && ((in[16] == 0x03 && in[17] == 0x00) ||
+ (in[16] == 0x00 && in[17] == 0x03))
+}
+
+// ElfDump matches a core dump file.
+func ElfDump(in []byte) bool {
+ return len(in) > 17 && ((in[16] == 0x04 && in[17] == 0x00) ||
+ (in[16] == 0x00 && in[17] == 0x04))
+}
+
+// Dcm matches a DICOM medical format file.
+func Dcm(in []byte) bool {
+ return len(in) > 131 &&
+ bytes.Equal(in[128:132], []byte{0x44, 0x49, 0x43, 0x4D})
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/database.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/database.go
new file mode 100644
index 000000000..89901044a
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/database.go
@@ -0,0 +1,10 @@
+package matchers
+
+import "bytes"
+
+// Sqlite matches an SQLite database file.
+func Sqlite(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{
+ 0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00,
+ })
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/document.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/document.go
new file mode 100644
index 000000000..c853f5850
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/document.go
@@ -0,0 +1,35 @@
+package matchers
+
+import "bytes"
+
+// Pdf matches a Portable Document Format file.
+func Pdf(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x25, 0x50, 0x44, 0x46})
+}
+
+// DjVu matches a DjVu file
+func DjVu(in []byte) bool {
+ if !bytes.HasPrefix(in, []byte{0x41, 0x54, 0x26, 0x54, 0x46, 0x4F, 0x52, 0x4D}) {
+ return false
+ }
+ if len(in) < 15 {
+ return false
+ }
+ return bytes.HasPrefix(in[12:], []byte("DJVM")) ||
+ bytes.HasPrefix(in[12:], []byte("DJVU")) ||
+ bytes.HasPrefix(in[12:], []byte("DJVI")) ||
+ bytes.HasPrefix(in[12:], []byte("THUM"))
+}
+
+// Mobi matches a Mobi file
+func Mobi(in []byte) bool {
+ if len(in) < 68 {
+ return false
+ }
+ return bytes.Equal(in[60:68], []byte("BOOKMOBI"))
+}
+
+// Lit matches a Microsoft Lit file
+func Lit(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("ITOLITLS"))
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/font.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/font.go
new file mode 100644
index 000000000..11ae659d4
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/font.go
@@ -0,0 +1,27 @@
+package matchers
+
+import "bytes"
+
+// Woff matches a Web Open Font Format file.
+func Woff(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("wOFF"))
+}
+
+// Woff2 matches a Web Open Font Format version 2 file.
+func Woff2(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("wOF2"))
+}
+
+// Otf matches an OpenType font file.
+func Otf(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x4F, 0x54, 0x54, 0x4F, 0x00})
+}
+
+// Eot matches an Embedded OpenType font file.
+func Eot(in []byte) bool {
+ return len(in) > 35 &&
+ bytes.Equal(in[34:36], []byte{0x4C, 0x50}) &&
+ (bytes.Equal(in[8:11], []byte{0x02, 0x00, 0x01}) ||
+ bytes.Equal(in[8:11], []byte{0x01, 0x00, 0x00}) ||
+ bytes.Equal(in[8:11], []byte{0x02, 0x00, 0x02}))
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/geo.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/geo.go
new file mode 100644
index 000000000..86f46305a
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/geo.go
@@ -0,0 +1,44 @@
+package matchers
+
+import (
+ "bytes"
+ "encoding/binary"
+)
+
+// Shp matches a shape format file.
+// https://www.esri.com/library/whitepapers/pdfs/shapefile.pdf
+func Shp(in []byte) bool {
+ if len(in) < 112 {
+ return false
+ }
+ shapeTypes := []int{
+ 0, // Null shape
+ 1, // Point
+ 3, // Polyline
+ 5, // Polygon
+ 8, // MultiPoint
+ 11, // PointZ
+ 13, // PolylineZ
+ 15, // PolygonZ
+ 18, // MultiPointZ
+ 21, // PointM
+ 23, // PolylineM
+ 25, // PolygonM
+ 28, // MultiPointM
+ 31, // MultiPatch
+ }
+
+ for _, st := range shapeTypes {
+ if st == int(binary.LittleEndian.Uint32(in[108:112])) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Shx matches a shape index format file.
+// https://www.esri.com/library/whitepapers/pdfs/shapefile.pdf
+func Shx(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x00, 0x00, 0x27, 0x0A})
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/image.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/image.go
new file mode 100644
index 000000000..e2a5827ee
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/image.go
@@ -0,0 +1,91 @@
+package matchers
+
+import "bytes"
+
+// Png matches a Portable Network Graphics file.
+func Png(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A})
+}
+
+// Jpg matches a Joint Photographic Experts Group file.
+func Jpg(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0xFF, 0xD8, 0xFF})
+}
+
+// Gif matches a Graphics Interchange Format file.
+func Gif(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("GIF87a")) ||
+ bytes.HasPrefix(in, []byte("GIF89a"))
+}
+
+// Webp matches a WebP file.
+func Webp(in []byte) bool {
+ return len(in) > 12 &&
+ bytes.Equal(in[0:4], []byte{0x52, 0x49, 0x46, 0x46}) &&
+ bytes.Equal(in[8:12], []byte{0x57, 0x45, 0x42, 0x50})
+}
+
+// Bmp matches a bitmap image file.
+func Bmp(in []byte) bool {
+ return len(in) > 1 && in[0] == 0x42 && in[1] == 0x4D
+}
+
+// Ps matches a PostScript file.
+func Ps(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("%!PS-Adobe-"))
+}
+
+// Psd matches a Photoshop Document file.
+func Psd(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("8BPS"))
+}
+
+// Ico matches an ICO file.
+func Ico(in []byte) bool {
+ return len(in) > 3 &&
+ in[0] == 0x00 && in[1] == 0x00 &&
+ in[2] == 0x01 && in[3] == 0x00
+}
+
+// Tiff matches a Tagged Image File Format file.
+func Tiff(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x49, 0x49, 0x2A, 0x00}) ||
+ bytes.HasPrefix(in, []byte{0x4D, 0x4D, 0x00, 0x2A})
+}
+
+// Bpg matches a Better Portable Graphics file.
+func Bpg(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x42, 0x50, 0x47, 0xFB})
+}
+
+// Dwg matches a CAD drawing file.
+func Dwg(in []byte) bool {
+ if len(in) < 6 || in[0] != 0x41 || in[1] != 0x43 {
+ return false
+ }
+ dwgVersions := [][]byte{
+ {0x31, 0x2E, 0x34, 0x30},
+ {0x31, 0x2E, 0x35, 0x30},
+ {0x32, 0x2E, 0x31, 0x30},
+ {0x31, 0x30, 0x30, 0x32},
+ {0x31, 0x30, 0x30, 0x33},
+ {0x31, 0x30, 0x30, 0x34},
+ {0x31, 0x30, 0x30, 0x36},
+ {0x31, 0x30, 0x30, 0x39},
+ {0x31, 0x30, 0x31, 0x32},
+ {0x31, 0x30, 0x31, 0x34},
+ {0x31, 0x30, 0x31, 0x35},
+ {0x31, 0x30, 0x31, 0x38},
+ {0x31, 0x30, 0x32, 0x31},
+ {0x31, 0x30, 0x32, 0x34},
+ {0x31, 0x30, 0x33, 0x32},
+ }
+
+ for _, d := range dwgVersions {
+ if bytes.Equal(in[2:6], d) {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/matchers.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/matchers.go
new file mode 100644
index 000000000..6170443a8
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/matchers.go
@@ -0,0 +1,46 @@
+// Package matchers holds the matching functions used to find mime types.
+package matchers
+
+// ReadLimit is the maximum number of bytes read
+// from the input when detecting a reader.
+const ReadLimit = 2048
+
+// True is a dummy matching function used to match any input.
+func True([]byte) bool {
+ return true
+}
+
+// False is a dummy matching function used to never match input.
+func False([]byte) bool {
+ return false
+}
+
+// trimLWS trims whitespace from beginning of the input.
+func trimLWS(in []byte) []byte {
+ firstNonWS := 0
+ for ; firstNonWS < len(in) && isWS(in[firstNonWS]); firstNonWS++ {
+ }
+
+ return in[firstNonWS:]
+}
+
+// trimRWS trims whitespace from the end of the input.
+func trimRWS(in []byte) []byte {
+ lastNonWS := len(in) - 1
+ for ; lastNonWS > 0 && isWS(in[lastNonWS]); lastNonWS-- {
+ }
+
+ return in[:lastNonWS+1]
+}
+
+func firstLine(in []byte) []byte {
+ lineEnd := 0
+ for ; lineEnd < len(in) && in[lineEnd] != '\n'; lineEnd++ {
+ }
+
+ return in[:lineEnd]
+}
+
+func isWS(b byte) bool {
+ return b == '\t' || b == '\n' || b == '\x0c' || b == '\r' || b == ' '
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/ms_office.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/ms_office.go
new file mode 100644
index 000000000..dadc48ae0
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/ms_office.go
@@ -0,0 +1,73 @@
+package matchers
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+)
+
+// Xlsx matches a Microsoft Excel 2007 file.
+func Xlsx(in []byte) bool {
+ return bytes.Contains(in, []byte("xl/"))
+}
+
+// Docx matches a Microsoft Office 2007 file.
+func Docx(in []byte) bool {
+ return bytes.Contains(in, []byte("word/"))
+}
+
+// Pptx matches a Microsoft PowerPoint 2007 file.
+func Pptx(in []byte) bool {
+ return bytes.Contains(in, []byte("ppt/"))
+}
+
+// Doc matches a Microsoft Office 97-2003 file.
+func Doc(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1})
+}
+
+// Ppt matches a Microsoft PowerPoint 97-2003 file.
+func Ppt(in []byte) bool {
+ if len(in) < 520 {
+ return false
+ }
+
+ if fmt.Sprintf("%X", in[:8]) == "D0CF11E0A1B11AE1" {
+ offset512 := fmt.Sprintf("%X", in[512:516])
+ if offset512 == "A0461DF0" || offset512 == "006E1EF0" || offset512 == "0F00E803" {
+ return true
+ }
+ if offset512 == "FDFFFFFF" && fmt.Sprintf("%x", in[518:520]) == "0000" {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Xls matches a Microsoft Excel 97-2003 file.
+func Xls(in []byte) bool {
+ if len(in) < 520 {
+ return false
+ }
+
+ if fmt.Sprintf("%X", in[:8]) == "D0CF11E0A1B11AE1" {
+ offset512 := fmt.Sprintf("%X", in[512:520])
+ subheaders := []string{
+ "0908100000060500",
+ "FDFFFFFF10",
+ "FDFFFFFF1F",
+ "FDFFFFFF22",
+ "FDFFFFFF23",
+ "FDFFFFFF28",
+ "FDFFFFFF29",
+ }
+ for _, h := range subheaders {
+ if strings.HasPrefix(offset512, h) {
+ return true
+ }
+ }
+ }
+
+ return false
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/odf.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/odf.go
new file mode 100644
index 000000000..ad31e666b
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/odf.go
@@ -0,0 +1,48 @@
+package matchers
+
+import "bytes"
+
+// Odt matches an OpenDocument Text file.
+func Odt(in []byte) bool {
+ return bytes.Contains(in, []byte("mimetypeapplication/vnd.oasis.opendocument.text"))
+}
+
+// Ott matches an OpenDocument Text Template file.
+func Ott(in []byte) bool {
+ return bytes.Contains(in, []byte("mimetypeapplication/vnd.oasis.opendocument.text-template"))
+}
+
+// Ods matches an OpenDocument Spreadsheet file.
+func Ods(in []byte) bool {
+ return bytes.Contains(in, []byte("mimetypeapplication/vnd.oasis.opendocument.spreadsheet"))
+}
+
+// Ots matches an OpenDocument Spreadsheet Template file.
+func Ots(in []byte) bool {
+ return bytes.Contains(in, []byte("mimetypeapplication/vnd.oasis.opendocument.spreadsheet-template"))
+}
+
+// Odp matches an OpenDocument Presentation file.
+func Odp(in []byte) bool {
+ return bytes.Contains(in, []byte("mimetypeapplication/vnd.oasis.opendocument.presentation"))
+}
+
+// Otp matches an OpenDocument Presentation Template file.
+func Otp(in []byte) bool {
+ return bytes.Contains(in, []byte("mimetypeapplication/vnd.oasis.opendocument.presentation-template"))
+}
+
+// Odg matches an OpenDocument Drawing file.
+func Odg(in []byte) bool {
+ return bytes.Contains(in, []byte("mimetypeapplication/vnd.oasis.opendocument.graphics"))
+}
+
+// Otg matches an OpenDocument Drawing Template file.
+func Otg(in []byte) bool {
+ return bytes.Contains(in, []byte("mimetypeapplication/vnd.oasis.opendocument.graphics-template"))
+}
+
+// Odf matches an OpenDocument Formula file.
+func Odf(in []byte) bool {
+ return bytes.Contains(in, []byte("mimetypeapplication/vnd.oasis.opendocument.formula"))
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/signature.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/signature.go
new file mode 100644
index 000000000..75267090f
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/signature.go
@@ -0,0 +1,128 @@
+package matchers
+
+import (
+ "bytes"
+ "fmt"
+)
+
+type (
+ markupSig []byte
+ ciSig []byte // case insensitive signature
+ shebangSig []byte // matches !# followed by the signature
+ ftypSig []byte // matches audio/video files. www.ftyps.com
+ xmlSig struct {
+ // the local name of the root tag
+ localName []byte
+ // the namespace of the XML document
+ xmlns []byte
+ }
+ sig interface {
+ detect([]byte) bool
+ }
+)
+
+func newXmlSig(localName, xmlns string) xmlSig {
+ ret := xmlSig{xmlns: []byte(xmlns)}
+ if localName != "" {
+ ret.localName = []byte(fmt.Sprintf("<%s", localName))
+ }
+
+ return ret
+}
+
+// Implement sig interface.
+func (hSig markupSig) detect(in []byte) bool {
+ if len(in) < len(hSig)+1 {
+ return false
+ }
+
+ // perform case insensitive check
+ for i, b := range hSig {
+ db := in[i]
+ if 'A' <= b && b <= 'Z' {
+ db &= 0xDF
+ }
+ if b != db {
+ return false
+ }
+ }
+ // Next byte must be space or right angle bracket.
+ if db := in[len(hSig)]; db != ' ' && db != '>' {
+ return false
+ }
+
+ return true
+}
+
+// Implement sig interface.
+func (tSig ciSig) detect(in []byte) bool {
+ if len(in) < len(tSig)+1 {
+ return false
+ }
+
+ // perform case insensitive check
+ for i, b := range tSig {
+ db := in[i]
+ if 'A' <= b && b <= 'Z' {
+ db &= 0xDF
+ }
+ if b != db {
+ return false
+ }
+ }
+
+ return true
+}
+
+// a valid shebang starts with the "#!" characters
+// followed by any number of spaces
+// followed by the path to the interpreter and optionally, the args for the interpreter
+func (sSig shebangSig) detect(in []byte) bool {
+ in = firstLine(in)
+
+ if len(in) < len(sSig)+2 {
+ return false
+ }
+ if in[0] != '#' || in[1] != '!' {
+ return false
+ }
+
+ in = trimLWS(trimRWS(in[2:]))
+
+ return bytes.Equal(in, sSig)
+}
+
+// Implement sig interface.
+func (fSig ftypSig) detect(in []byte) bool {
+ return len(in) > 12 &&
+ bytes.Equal(in[4:8], []byte("ftyp")) &&
+ bytes.Equal(in[8:12], fSig)
+}
+
+func (xSig xmlSig) detect(in []byte) bool {
+ l := 512
+ if len(in) < l {
+ l = len(in)
+ }
+ in = in[:l]
+
+ if len(xSig.localName) == 0 {
+ return bytes.Index(in, xSig.xmlns) > 0
+ }
+ if len(xSig.xmlns) == 0 {
+ return bytes.Index(in, xSig.localName) > 0
+ }
+
+ localNameIndex := bytes.Index(in, xSig.localName)
+ return localNameIndex != -1 && localNameIndex < bytes.Index(in, xSig.xmlns)
+}
+
+func detect(in []byte, sigs []sig) bool {
+ for _, sig := range sigs {
+ if sig.detect(in) {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/text.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/text.go
new file mode 100644
index 000000000..e9ca91c44
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/text.go
@@ -0,0 +1,304 @@
+package matchers
+
+import (
+ "bytes"
+
+ "github.com/gabriel-vasile/mimetype/internal/json"
+)
+
+var (
+ htmlSigs = []sig{
+ markupSig(" 1 && len(lines) > 1
+}
+
+// butLastLineReader returns a reader to the provided byte slice.
+// the reader is guaranteed to reach EOF before it reads `cutAt` bytes.
+// bytes after the last newline are dropped from the input.
+func butLastLineReader(in []byte, cutAt int) io.Reader {
+ if len(in) >= cutAt {
+ for i := cutAt - 1; i > 0; i-- {
+ if in[i] == '\n' {
+ return bytes.NewReader(in[:i])
+ }
+ }
+
+ // no newline was found between the 0 index and cutAt
+ return bytes.NewReader(in[:cutAt])
+ }
+
+ return bytes.NewReader(in)
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/video.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/video.go
new file mode 100644
index 000000000..2c1fcc1a1
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/video.go
@@ -0,0 +1,70 @@
+package matchers
+
+import (
+ "bytes"
+)
+
+// WebM matches a WebM file.
+func WebM(in []byte) bool {
+ return isMatroskaFileTypeMatched(in, "webm")
+}
+
+// Mkv matches a mkv file.
+func Mkv(in []byte) bool {
+ return isMatroskaFileTypeMatched(in, "matroska")
+}
+
+// isMatroskaFileTypeMatched is used for webm and mkv file matching.
+// It checks for .Eߣ sequence. If the sequence is found,
+// then it means it is Matroska media container, including WebM.
+// Then it verifies which of the file type it is representing by matching the
+// file specific string.
+func isMatroskaFileTypeMatched(in []byte, flType string) bool {
+ if bytes.HasPrefix(in, []byte("\x1A\x45\xDF\xA3")) {
+ return isFileTypeNamePresent(in, flType)
+ }
+ return false
+}
+
+// isFileTypeNamePresent accepts the matroska input data stream and searches
+// for the given file type in the stream. Return whether a match is found.
+// The logic of search is: find first instance of \x42\x82 and then
+// search for given string after one byte of above instance.
+func isFileTypeNamePresent(in []byte, flType string) bool {
+ var ind int
+ if len(in) >= 4096 { // restricting length to 4096
+ ind = bytes.Index(in[0:4096], []byte("\x42\x82"))
+ } else {
+ ind = bytes.Index(in, []byte("\x42\x82"))
+ }
+ if ind > 0 {
+ // filetype name will be present exactly
+ // one byte after the match of the two bytes "\x42\x82"
+ return bytes.HasPrefix(in[ind+3:], []byte(flType))
+ }
+ return false
+}
+
+// Flv matches a Flash video file.
+func Flv(in []byte) bool {
+ return bytes.HasPrefix(in, []byte("\x46\x4C\x56\x01"))
+}
+
+// Mpeg matches a Moving Picture Experts Group file.
+func Mpeg(in []byte) bool {
+ return bytes.HasPrefix(in, []byte{0x00, 0x00, 0x01}) &&
+ in[3] >= 0xB0 && in[3] <= 0xBF
+}
+
+// Avi matches an Audio Video Interleaved file.
+func Avi(in []byte) bool {
+ return len(in) > 16 &&
+ bytes.Equal(in[:4], []byte("RIFF")) &&
+ bytes.Equal(in[8:16], []byte("AVI LIST"))
+}
+
+// Asf matches an Advanced Systems Format file.
+func Asf(in []byte) bool {
+ return len(in) > 16 && bytes.Equal(in[:16], []byte{0x30, 0x26, 0xB2, 0x75,
+ 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C})
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/video_ftyp.go b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/video_ftyp.go
new file mode 100644
index 000000000..0d470cd3c
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/matchers/video_ftyp.go
@@ -0,0 +1,70 @@
+package matchers
+
+var (
+ mp4Sigs = []sig{
+ ftypSig("avc1"), ftypSig("dash"), ftypSig("iso2"), ftypSig("iso3"),
+ ftypSig("iso4"), ftypSig("iso5"), ftypSig("iso6"), ftypSig("isom"),
+ ftypSig("mmp4"), ftypSig("mp41"), ftypSig("mp42"), ftypSig("mp4v"),
+ ftypSig("mp71"), ftypSig("MSNV"), ftypSig("NDAS"), ftypSig("NDSC"),
+ ftypSig("NSDC"), ftypSig("NSDH"), ftypSig("NDSM"), ftypSig("NDSP"),
+ ftypSig("NDSS"), ftypSig("NDXC"), ftypSig("NDXH"), ftypSig("NDXM"),
+ ftypSig("NDXP"), ftypSig("NDXS"), ftypSig("F4V "), ftypSig("F4P "),
+ }
+ threeGPSigs = []sig{
+ ftypSig("3gp1"), ftypSig("3gp2"), ftypSig("3gp3"), ftypSig("3gp4"),
+ ftypSig("3gp5"), ftypSig("3gp6"), ftypSig("3gs7"), ftypSig("3ge6"),
+ ftypSig("3ge7"), ftypSig("3gg6"),
+ }
+ threeG2Sigs = []sig{
+ ftypSig("3g2a"), ftypSig("3g2b"), ftypSig("3g2c"), ftypSig("KDDI"),
+ }
+ amp4Sigs = []sig{
+ // audio for Adobe Flash Player 9+
+ ftypSig("F4A "), ftypSig("F4B "),
+ // Apple iTunes AAC-LC (.M4A) Audio
+ ftypSig("M4B "), ftypSig("M4P "),
+ // MPEG-4 (.MP4) for SonyPSP
+ ftypSig("MSNV"),
+ // Nero Digital AAC Audio
+ ftypSig("NDAS"),
+ }
+ qtSigs = []sig{ftypSig("qt "), ftypSig("moov")}
+ mqvSigs = []sig{ftypSig("mqt ")}
+ m4aSigs = []sig{ftypSig("M4A ")}
+ // TODO: add support for remaining video formats at ftyps.com.
+)
+
+// Mp4 matches an MP4 file.
+func Mp4(in []byte) bool {
+ return detect(in, mp4Sigs)
+}
+
+// ThreeGP matches a 3GPP file.
+func ThreeGP(in []byte) bool {
+ return detect(in, threeGPSigs)
+}
+
+// ThreeG2 matches a 3GPP2 file.
+func ThreeG2(in []byte) bool {
+ return detect(in, threeG2Sigs)
+}
+
+// AMp4 matches an audio MP4 file.
+func AMp4(in []byte) bool {
+ return detect(in, amp4Sigs)
+}
+
+// QuickTime matches a QuickTime File Format file.
+func QuickTime(in []byte) bool {
+ return detect(in, qtSigs)
+}
+
+// Mqv matches a Sony / Mobile QuickTime file.
+func Mqv(in []byte) bool {
+ return detect(in, mqvSigs)
+}
+
+// M4a matches an audio M4A file.
+func M4a(in []byte) bool {
+ return detect(in, m4aSigs)
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/mime.go b/vendor/github.com/gabriel-vasile/mimetype/mime.go
new file mode 100644
index 000000000..5992a2c4e
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/mime.go
@@ -0,0 +1,55 @@
+// Package mimetype uses magic number signatures
+// to detect the MIME type and extension of a file.
+package mimetype
+
+import (
+ "io"
+ "os"
+
+ "github.com/gabriel-vasile/mimetype/internal/matchers"
+)
+
+// Detect returns the MIME type and extension of the provided byte slice.
+//
+// mime is always a valid MIME type, with application/octet-stream as fallback.
+// extension is empty string if detected file format does not have an extension.
+func Detect(in []byte) (mime, extension string) {
+ if len(in) == 0 {
+ return "inode/x-empty", ""
+ }
+ n := root.match(in, root)
+ return n.mime, n.extension
+}
+
+// DetectReader returns the MIME type and extension
+// of the byte slice read from the provided reader.
+//
+// mime is always a valid MIME type, with application/octet-stream as fallback.
+// extension is empty string if detection failed with an error or
+// detected file format does not have an extension.
+func DetectReader(r io.Reader) (mime, extension string, err error) {
+ in := make([]byte, matchers.ReadLimit)
+ n, err := r.Read(in)
+ if err != nil && err != io.EOF {
+ return root.mime, root.extension, err
+ }
+ in = in[:n]
+
+ mime, extension = Detect(in)
+ return mime, extension, nil
+}
+
+// DetectFile returns the MIME type and extension of the provided file.
+//
+// mime is always a valid MIME type, with application/octet-stream as fallback.
+// extension is empty string if detection failed with an error or
+// detected file format does not have an extension.
+func DetectFile(file string) (mime, extension string, err error) {
+ f, err := os.Open(file)
+ if err != nil {
+ return root.mime, root.extension, err
+ }
+ defer f.Close()
+
+ return DetectReader(f)
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/mimetype.gif b/vendor/github.com/gabriel-vasile/mimetype/mimetype.gif
new file mode 100644
index 000000000..912aa5776
Binary files /dev/null and b/vendor/github.com/gabriel-vasile/mimetype/mimetype.gif differ
diff --git a/vendor/github.com/gabriel-vasile/mimetype/node.go b/vendor/github.com/gabriel-vasile/mimetype/node.go
new file mode 100644
index 000000000..721c76b8c
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/node.go
@@ -0,0 +1,43 @@
+package mimetype
+
+type (
+ // node represents a vertex in the matchers tree structure.
+ // It holds the mime type, the extension and the function
+ // to check whether a byte slice has the mime type.
+ node struct {
+ mime string
+ extension string
+ matchFunc func([]byte) bool
+ children []*node
+ }
+)
+
+func newNode(mime, extension string, matchFunc func([]byte) bool, children ...*node) *node {
+ return &node{
+ mime: mime,
+ extension: extension,
+ matchFunc: matchFunc,
+ children: children,
+ }
+}
+
+// match does a depth-first search on the matchers tree.
+// it returns the deepest successful matcher for which all the children fail.
+func (n *node) match(in []byte, deepestMatch *node) *node {
+ for _, c := range n.children {
+ if c.matchFunc(in) {
+ return c.match(in, c)
+ }
+ }
+
+ return deepestMatch
+}
+
+func (n *node) flatten() []*node {
+ out := []*node{n}
+ for _, c := range n.children {
+ out = append(out, c.flatten()...)
+ }
+
+ return out
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/supported_mimes.md b/vendor/github.com/gabriel-vasile/mimetype/supported_mimes.md
new file mode 100644
index 000000000..abe6cb36c
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/supported_mimes.md
@@ -0,0 +1,119 @@
+## 114 Supported MIME types
+This file is automatically generated when running tests. Do not edit manually.
+
+Extension | MIME type
+--------- | --------
+**n/a** | application/octet-stream
+**7z** | application/x-7z-compressed
+**zip** | application/zip
+**xlsx** | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
+**docx** | application/vnd.openxmlformats-officedocument.wordprocessingml.document
+**pptx** | application/vnd.openxmlformats-officedocument.presentationml.presentation
+**epub** | application/epub+zip
+**jar** | application/jar
+**odt** | application/vnd.oasis.opendocument.text
+**ott** | application/vnd.oasis.opendocument.text-template
+**ods** | application/vnd.oasis.opendocument.spreadsheet
+**ots** | application/vnd.oasis.opendocument.spreadsheet-template
+**odp** | application/vnd.oasis.opendocument.presentation
+**otp** | application/vnd.oasis.opendocument.presentation-template
+**odg** | application/vnd.oasis.opendocument.graphics
+**otg** | application/vnd.oasis.opendocument.graphics-template
+**odf** | application/vnd.oasis.opendocument.formula
+**pdf** | application/pdf
+**xls** | application/vnd.ms-excel
+**ppt** | application/vnd.ms-powerpoint
+**doc** | application/msword
+**ps** | application/postscript
+**psd** | application/x-photoshop
+**ogg** | application/ogg
+**png** | image/png
+**jpg** | image/jpeg
+**gif** | image/gif
+**webp** | image/webp
+**exe** | application/vnd.microsoft.portable-executable
+**n/a** | application/x-elf
+**n/a** | application/x-object
+**n/a** | application/x-executable
+**so** | application/x-sharedlib
+**n/a** | application/x-coredump
+**a** | application/x-archive
+**deb** | application/vnd.debian.binary-package
+**tar** | application/x-tar
+**xar** | application/x-xar
+**bz2** | application/x-bzip2
+**fits** | application/fits
+**tiff** | image/tiff
+**bmp** | image/bmp
+**ico** | image/x-icon
+**mp3** | audio/mpeg
+**flac** | audio/flac
+**midi** | audio/midi
+**ape** | audio/ape
+**mpc** | audio/musepack
+**amr** | audio/amr
+**wav** | audio/wav
+**aiff** | audio/aiff
+**au** | audio/basic
+**mpeg** | video/mpeg
+**mov** | video/quicktime
+**mqv** | video/quicktime
+**mp4** | video/mp4
+**webm** | video/webm
+**3gp** | video/3gpp
+**3g2** | video/3gpp2
+**avi** | video/x-msvideo
+**flv** | video/x-flv
+**mkv** | video/x-matroska
+**asf** | video/x-ms-asf
+**aac** | audio/aac
+**voc** | audio/x-unknown
+**mp4** | audio/mp4
+**m4a** | audio/x-m4a
+**txt** | text/plain
+**html** | text/html; charset=utf-8
+**svg** | image/svg+xml
+**xml** | text/xml; charset=utf-8
+**rss** | application/rss+xml
+**atom** | application/atom+xml
+**x3d** | model/x3d+xml
+**kml** | application/vnd.google-earth.kml+xml
+**xlf** | application/x-xliff+xml
+**dae** | model/vnd.collada+xml
+**gml** | application/gml+xml
+**gpx** | application/gpx+xml
+**tcx** | application/vnd.garmin.tcx+xml
+**amf** | application/x-amf
+**3mf** | application/vnd.ms-package.3dmanufacturing-3dmodel+xml
+**php** | text/x-php; charset=utf-8
+**js** | application/javascript
+**lua** | text/x-lua
+**pl** | text/x-perl
+**py** | application/x-python
+**json** | application/json
+**geojson** | application/geo+json
+**rtf** | text/rtf
+**tcl** | text/x-tcl
+**csv** | text/csv
+**tsv** | text/tab-separated-values
+**vcf** | text/vcard
+**gz** | application/gzip
+**class** | application/x-java-applet; charset=binary
+**swf** | application/x-shockwave-flash
+**crx** | application/x-chrome-extension
+**woff** | font/woff
+**woff2** | font/woff2
+**otf** | font/otf
+**eot** | application/vnd.ms-fontobject
+**wasm** | application/wasm
+**shx** | application/octet-stream
+**shp** | application/octet-stream
+**dbf** | application/x-dbf
+**dcm** | application/dicom
+**rar** | application/x-rar-compressed
+**djvu** | image/vnd.djvu
+**mobi** | application/x-mobipocket-ebook
+**lit** | application/x-ms-reader
+**bpg** | image/bpg
+**sqlite** | application/x-sqlite3
+**dwg** | image/vnd.dwg
diff --git a/vendor/github.com/gabriel-vasile/mimetype/tree.go b/vendor/github.com/gabriel-vasile/mimetype/tree.go
new file mode 100644
index 000000000..4543c3e32
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/tree.go
@@ -0,0 +1,131 @@
+package mimetype
+
+import "github.com/gabriel-vasile/mimetype/internal/matchers"
+
+// root is a matcher which passes for any slice of bytes.
+// When a matcher passes the check, the children matchers
+// are tried in order to find a more accurate mime type.
+var root = newNode("application/octet-stream", "", matchers.True,
+ sevenZ, zip, pdf, xls, ppt, doc, ps, psd, ogg, png, jpg, gif, webp, exe, elf,
+ ar, tar, xar, bz2, fits, tiff, bmp, ico, mp3, flac, midi, ape, musePack, amr,
+ wav, aiff, au, mpeg, quickTime, mqv, mp4, webM, threeGP, threeG2, avi, flv,
+ mkv, asf, aac, voc, aMp4, m4a, txt, gzip, class, swf, crx, woff, woff2, otf,
+ eot, wasm, shx, dbf, dcm, rar, djvu, mobi, lit, bpg, sqlite3, dwg,
+)
+
+// The list of nodes appended to the root node
+var (
+ gzip = newNode("application/gzip", "gz", matchers.Gzip)
+ sevenZ = newNode("application/x-7z-compressed", "7z", matchers.SevenZ)
+ zip = newNode("application/zip", "zip", matchers.Zip, xlsx, docx, pptx, epub, jar, odt, ods, odp, odg, odf)
+ tar = newNode("application/x-tar", "tar", matchers.Tar)
+ xar = newNode("application/x-xar", "xar", matchers.Xar)
+ bz2 = newNode("application/x-bzip2", "bz2", matchers.Bz2)
+ pdf = newNode("application/pdf", "pdf", matchers.Pdf)
+ xlsx = newNode("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx", matchers.Xlsx)
+ docx = newNode("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx", matchers.Docx)
+ pptx = newNode("application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx", matchers.Pptx)
+ epub = newNode("application/epub+zip", "epub", matchers.Epub)
+ jar = newNode("application/jar", "jar", matchers.Jar)
+ doc = newNode("application/msword", "doc", matchers.Doc)
+ ppt = newNode("application/vnd.ms-powerpoint", "ppt", matchers.Ppt)
+ xls = newNode("application/vnd.ms-excel", "xls", matchers.Xls)
+ ps = newNode("application/postscript", "ps", matchers.Ps)
+ psd = newNode("application/x-photoshop", "psd", matchers.Psd)
+ fits = newNode("application/fits", "fits", matchers.Fits)
+ ogg = newNode("application/ogg", "ogg", matchers.Ogg)
+ txt = newNode("text/plain", "txt", matchers.Txt, html, svg, xml, php, js, lua, perl, python, json, rtf, tcl, csv, tsv, vCard)
+ xml = newNode("text/xml; charset=utf-8", "xml", matchers.Xml, rss, atom, x3d, kml, xliff, collada, gml, gpx, tcx, amf, threemf)
+ json = newNode("application/json", "json", matchers.Json, geoJson)
+ csv = newNode("text/csv", "csv", matchers.Csv)
+ tsv = newNode("text/tab-separated-values", "tsv", matchers.Tsv)
+ geoJson = newNode("application/geo+json", "geojson", matchers.GeoJson)
+ html = newNode("text/html; charset=utf-8", "html", matchers.Html)
+ php = newNode("text/x-php; charset=utf-8", "php", matchers.Php)
+ rtf = newNode("text/rtf", "rtf", matchers.Rtf)
+ js = newNode("application/javascript", "js", matchers.Js)
+ lua = newNode("text/x-lua", "lua", matchers.Lua)
+ perl = newNode("text/x-perl", "pl", matchers.Perl)
+ python = newNode("application/x-python", "py", matchers.Python)
+ tcl = newNode("text/x-tcl", "tcl", matchers.Tcl)
+ vCard = newNode("text/vcard", "vcf", matchers.VCard)
+ svg = newNode("image/svg+xml", "svg", matchers.Svg)
+ rss = newNode("application/rss+xml", "rss", matchers.Rss)
+ atom = newNode("application/atom+xml", "atom", matchers.Atom)
+ x3d = newNode("model/x3d+xml", "x3d", matchers.X3d)
+ kml = newNode("application/vnd.google-earth.kml+xml", "kml", matchers.Kml)
+ xliff = newNode("application/x-xliff+xml", "xlf", matchers.Xliff)
+ collada = newNode("model/vnd.collada+xml", "dae", matchers.Collada)
+ gml = newNode("application/gml+xml", "gml", matchers.Gml)
+ gpx = newNode("application/gpx+xml", "gpx", matchers.Gpx)
+ tcx = newNode("application/vnd.garmin.tcx+xml", "tcx", matchers.Tcx)
+ amf = newNode("application/x-amf", "amf", matchers.Amf)
+ threemf = newNode("application/vnd.ms-package.3dmanufacturing-3dmodel+xml", "3mf", matchers.Threemf)
+ png = newNode("image/png", "png", matchers.Png)
+ jpg = newNode("image/jpeg", "jpg", matchers.Jpg)
+ bpg = newNode("image/bpg", "bpg", matchers.Bpg)
+ gif = newNode("image/gif", "gif", matchers.Gif)
+ webp = newNode("image/webp", "webp", matchers.Webp)
+ tiff = newNode("image/tiff", "tiff", matchers.Tiff)
+ bmp = newNode("image/bmp", "bmp", matchers.Bmp)
+ ico = newNode("image/x-icon", "ico", matchers.Ico)
+ mp3 = newNode("audio/mpeg", "mp3", matchers.Mp3)
+ flac = newNode("audio/flac", "flac", matchers.Flac)
+ midi = newNode("audio/midi", "midi", matchers.Midi)
+ ape = newNode("audio/ape", "ape", matchers.Ape)
+ musePack = newNode("audio/musepack", "mpc", matchers.MusePack)
+ wav = newNode("audio/wav", "wav", matchers.Wav)
+ aiff = newNode("audio/aiff", "aiff", matchers.Aiff)
+ au = newNode("audio/basic", "au", matchers.Au)
+ amr = newNode("audio/amr", "amr", matchers.Amr)
+ aac = newNode("audio/aac", "aac", matchers.Aac)
+ voc = newNode("audio/x-unknown", "voc", matchers.Voc)
+ aMp4 = newNode("audio/mp4", "mp4", matchers.AMp4)
+ m4a = newNode("audio/x-m4a", "m4a", matchers.M4a)
+ mp4 = newNode("video/mp4", "mp4", matchers.Mp4)
+ webM = newNode("video/webm", "webm", matchers.WebM)
+ mpeg = newNode("video/mpeg", "mpeg", matchers.Mpeg)
+ quickTime = newNode("video/quicktime", "mov", matchers.QuickTime)
+ mqv = newNode("video/quicktime", "mqv", matchers.Mqv)
+ threeGP = newNode("video/3gpp", "3gp", matchers.ThreeGP)
+ threeG2 = newNode("video/3gpp2", "3g2", matchers.ThreeG2)
+ avi = newNode("video/x-msvideo", "avi", matchers.Avi)
+ flv = newNode("video/x-flv", "flv", matchers.Flv)
+ mkv = newNode("video/x-matroska", "mkv", matchers.Mkv)
+ asf = newNode("video/x-ms-asf", "asf", matchers.Asf)
+ class = newNode("application/x-java-applet; charset=binary", "class", matchers.Class)
+ swf = newNode("application/x-shockwave-flash", "swf", matchers.Swf)
+ crx = newNode("application/x-chrome-extension", "crx", matchers.Crx)
+ woff = newNode("font/woff", "woff", matchers.Woff)
+ woff2 = newNode("font/woff2", "woff2", matchers.Woff2)
+ otf = newNode("font/otf", "otf", matchers.Otf)
+ eot = newNode("application/vnd.ms-fontobject", "eot", matchers.Eot)
+ wasm = newNode("application/wasm", "wasm", matchers.Wasm)
+ shp = newNode("application/octet-stream", "shp", matchers.Shp)
+ shx = newNode("application/octet-stream", "shx", matchers.Shx, shp)
+ dbf = newNode("application/x-dbf", "dbf", matchers.Dbf)
+ exe = newNode("application/vnd.microsoft.portable-executable", "exe", matchers.Exe)
+ elf = newNode("application/x-elf", "", matchers.Elf, elfObj, elfExe, elfLib, elfDump)
+ elfObj = newNode("application/x-object", "", matchers.ElfObj)
+ elfExe = newNode("application/x-executable", "", matchers.ElfExe)
+ elfLib = newNode("application/x-sharedlib", "so", matchers.ElfLib)
+ elfDump = newNode("application/x-coredump", "", matchers.ElfDump)
+ ar = newNode("application/x-archive", "a", matchers.Ar, deb)
+ deb = newNode("application/vnd.debian.binary-package", "deb", matchers.Deb)
+ dcm = newNode("application/dicom", "dcm", matchers.Dcm)
+ odt = newNode("application/vnd.oasis.opendocument.text", "odt", matchers.Odt, ott)
+ ott = newNode("application/vnd.oasis.opendocument.text-template", "ott", matchers.Ott)
+ ods = newNode("application/vnd.oasis.opendocument.spreadsheet", "ods", matchers.Ods, ots)
+ ots = newNode("application/vnd.oasis.opendocument.spreadsheet-template", "ots", matchers.Ots)
+ odp = newNode("application/vnd.oasis.opendocument.presentation", "odp", matchers.Odp, otp)
+ otp = newNode("application/vnd.oasis.opendocument.presentation-template", "otp", matchers.Otp)
+ odg = newNode("application/vnd.oasis.opendocument.graphics", "odg", matchers.Odg, otg)
+ otg = newNode("application/vnd.oasis.opendocument.graphics-template", "otg", matchers.Otg)
+ odf = newNode("application/vnd.oasis.opendocument.formula", "odf", matchers.Odf)
+ rar = newNode("application/x-rar-compressed", "rar", matchers.Rar)
+ djvu = newNode("image/vnd.djvu", "djvu", matchers.DjVu)
+ mobi = newNode("application/x-mobipocket-ebook", "mobi", matchers.Mobi)
+ lit = newNode("application/x-ms-reader", "lit", matchers.Lit)
+ sqlite3 = newNode("application/x-sqlite3", "sqlite", matchers.Sqlite)
+ dwg = newNode("image/vnd.dwg", "dwg", matchers.Dwg)
+)