diff --git a/go.mod b/go.mod
index b4f76106..6f98937a 100644
--- a/go.mod
+++ b/go.mod
@@ -19,7 +19,7 @@ require (
github.com/dromara/carbon/v2 v2.6.14
github.com/eclipse/paho.mqtt.golang v1.5.0
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
- github.com/go-playground/validator/v10 v10.27.0
+ github.com/go-playground/validator/v10 v10.28.0
github.com/hashicorp/nomad/api v0.0.0-20250812204832-62b195aaa535 // v1.10.4
github.com/jedib0t/go-pretty/v6 v6.6.8
github.com/matcornic/hermes/v2 v2.1.0
@@ -76,7 +76,7 @@ require (
github.com/felixge/fgprof v0.9.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
- github.com/gabriel-vasile/mimetype v1.4.8 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
diff --git a/go.sum b/go.sum
index 6890b993..79d0eebf 100644
--- a/go.sum
+++ b/go.sum
@@ -111,8 +111,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
-github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
-github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
+github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
+github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E=
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -132,8 +132,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
-github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
+github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
+github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
diff --git a/vendor/github.com/gabriel-vasile/mimetype/.golangci.yml b/vendor/github.com/gabriel-vasile/mimetype/.golangci.yml
new file mode 100644
index 00000000..f2058ccc
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/.golangci.yml
@@ -0,0 +1,5 @@
+version: "2"
+linters:
+ exclusions:
+ presets:
+ - std-error-handling
diff --git a/vendor/github.com/gabriel-vasile/mimetype/CODE_OF_CONDUCT.md b/vendor/github.com/gabriel-vasile/mimetype/CODE_OF_CONDUCT.md
deleted file mode 100644
index 8479cd87..00000000
--- a/vendor/github.com/gabriel-vasile/mimetype/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# 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
deleted file mode 100644
index 56ae4e57..00000000
--- a/vendor/github.com/gabriel-vasile/mimetype/CONTRIBUTING.md
+++ /dev/null
@@ -1,12 +0,0 @@
-## 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/README.md b/vendor/github.com/gabriel-vasile/mimetype/README.md
index aa88b4bd..f28f56c9 100644
--- a/vendor/github.com/gabriel-vasile/mimetype/README.md
+++ b/vendor/github.com/gabriel-vasile/mimetype/README.md
@@ -27,6 +27,7 @@
- possibility to [extend](https://pkg.go.dev/github.com/gabriel-vasile/mimetype#example-package-Extend) with other file formats
- common file formats are prioritized
- [text vs. binary files differentiation](https://pkg.go.dev/github.com/gabriel-vasile/mimetype#example-package-TextVsBinary)
+- no external dependencies
- safe for concurrent usage
## Install
@@ -45,8 +46,7 @@ fmt.Println(mtype.String(), mtype.Extension())
```
See the [runnable Go Playground examples](https://pkg.go.dev/github.com/gabriel-vasile/mimetype#pkg-overview).
-## Usage'
-Only use libraries like **mimetype** as a last resort. Content type detection
+Caution: only use libraries like **mimetype** as a last resort. Content type detection
using magic numbers is slow, inaccurate, and non-standard. Most of the times
protocols have methods for specifying such metadata; e.g., `Content-Type` header
in HTTP and SMTP.
@@ -67,6 +67,18 @@ mimetype.DetectFile("file.doc")
If increasing the limit does not help, please
[open an issue](https://github.com/gabriel-vasile/mimetype/issues/new?assignees=&labels=&template=mismatched-mime-type-detected.md&title=).
+## Tests
+In addition to unit tests,
+[mimetype_tests](https://github.com/gabriel-vasile/mimetype_tests) compares the
+library with the [Unix file utility](https://en.wikipedia.org/wiki/File_(command))
+for around 50 000 sample files. Check the latest comparison results
+[here](https://github.com/gabriel-vasile/mimetype_tests/actions).
+
+## Benchmarks
+Benchmarks for each file format are performed when a PR is open. The results can
+be seen on the [workflows page](https://github.com/gabriel-vasile/mimetype/actions/workflows/benchmark.yml).
+Performance improvements are welcome but correctness is prioritized.
+
## Structure
**mimetype** uses a hierarchical structure to keep the MIME type detection logic.
This reduces the number of calls needed for detecting the file type. The reason
@@ -84,19 +96,8 @@ or from a [file](https://pkg.go.dev/github.com/gabriel-vasile/mimetype#DetectFil
-## Performance
-Thanks to the hierarchical structure, searching for common formats first,
-and limiting itself to file headers, **mimetype** matches the performance of
-stdlib `http.DetectContentType` while outperforming the alternative package.
-
-```bash
- mimetype http.DetectContentType filetype
-BenchmarkMatchTar-24 250 ns/op 400 ns/op 3778 ns/op
-BenchmarkMatchZip-24 524 ns/op 351 ns/op 4884 ns/op
-BenchmarkMatchJpeg-24 103 ns/op 228 ns/op 839 ns/op
-BenchmarkMatchGif-24 139 ns/op 202 ns/op 751 ns/op
-BenchmarkMatchPng-24 165 ns/op 221 ns/op 1176 ns/op
-```
-
## Contributing
-See [CONTRIBUTING.md](CONTRIBUTING.md).
+Contributions are unexpected but welcome. When submitting a PR for detection of
+a new file format, please make sure to add a record to the list of testcases
+from [mimetype_test.go](mimetype_test.go). For complex files a record can be added
+in the [testdata](testdata) directory.
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/charset/charset.go b/vendor/github.com/gabriel-vasile/mimetype/internal/charset/charset.go
index 0647f730..8c5a05e4 100644
--- a/vendor/github.com/gabriel-vasile/mimetype/internal/charset/charset.go
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/charset/charset.go
@@ -2,11 +2,10 @@ package charset
import (
"bytes"
- "encoding/xml"
- "strings"
"unicode/utf8"
- "golang.org/x/net/html"
+ "github.com/gabriel-vasile/mimetype/internal/markup"
+ "github.com/gabriel-vasile/mimetype/internal/scan"
)
const (
@@ -141,20 +140,31 @@ func FromXML(content []byte) string {
}
return FromPlain(content)
}
-func fromXML(content []byte) string {
- content = trimLWS(content)
- dec := xml.NewDecoder(bytes.NewReader(content))
- rawT, err := dec.RawToken()
- if err != nil {
- return ""
+func fromXML(s scan.Bytes) string {
+ xml := []byte(" 0 && line[n-1] == '\r' {
+ return line[:n-1], false // drop \r at end of line
+ }
+
+ // This line is problematic. The logic from CountFields comes from
+ // encoding/csv.Reader which relies on mutating the input bytes.
+ // https://github.com/golang/go/blob/b3251514531123d7fd007682389bce7428d159a0/src/encoding/csv/reader.go#L275-L279
+ // To avoid mutating the input, we return cutShort. #680
+ if n >= 2 && line[n-2] == '\r' && line[n-1] == '\n' {
+ return line[:n-2], true
+ }
+ return line, false
+}
+
+// CountFields reads one CSV line and counts how many records that line contained.
+// hasMore reports whether there are more lines in the input.
+// collectIndexes makes CountFields return a list of indexes where CSV fields
+// start in the line. These indexes are used to test the correctness against the
+// encoding/csv parser.
+func (r *Parser) CountFields(collectIndexes bool) (fields int, fieldPos []int, hasMore bool) {
+ finished := false
+ var line scan.Bytes
+ cutShort := false
+ for {
+ line, cutShort = r.readLine()
+ if finished {
+ return 0, nil, false
+ }
+ finished = len(r.s) == 0 && len(line) == 0
+ if len(line) == lengthNL(line) {
+ line = nil
+ continue // Skip empty lines.
+ }
+ if len(line) > 0 && line[0] == r.comment {
+ line = nil
+ continue
+ }
+ break
+ }
+
+ indexes := []int{}
+ originalLine := line
+parseField:
+ for {
+ if len(line) == 0 || line[0] != '"' { // non-quoted string field
+ fields++
+ if collectIndexes {
+ indexes = append(indexes, len(originalLine)-len(line))
+ }
+ i := bytes.IndexByte(line, r.comma)
+ if i >= 0 {
+ line.Advance(i + 1) // 1 to get over ending comma
+ continue parseField
+ }
+ break parseField
+ } else { // Quoted string field.
+ if collectIndexes {
+ indexes = append(indexes, len(originalLine)-len(line))
+ }
+ line.Advance(1) // get over starting quote
+ for {
+ i := bytes.IndexByte(line, '"')
+ if i >= 0 {
+ line.Advance(i + 1) // 1 for ending quote
+ switch rn := line.Peek(); {
+ case rn == '"':
+ line.Advance(1)
+ case rn == r.comma:
+ line.Advance(1)
+ fields++
+ continue parseField
+ case lengthNL(line) == len(line):
+ fields++
+ break parseField
+ }
+ } else if len(line) > 0 || cutShort {
+ line, cutShort = r.readLine()
+ originalLine = line
+ } else {
+ fields++
+ break parseField
+ }
+ }
+ }
+ }
+
+ return fields, indexes, fields != 0
+}
+
+// lengthNL reports the number of bytes for the trailing \n.
+func lengthNL(b []byte) int {
+ if len(b) > 0 && b[len(b)-1] == '\n' {
+ return 1
+ }
+ return 0
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/json/json.go b/vendor/github.com/gabriel-vasile/mimetype/internal/json/json.go
deleted file mode 100644
index 5b2ecee4..00000000
--- a/vendor/github.com/gabriel-vasile/mimetype/internal/json/json.go
+++ /dev/null
@@ -1,567 +0,0 @@
-// 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.
-
-// Package json provides a 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"
- "sync"
-)
-
-type (
- scanStatus int
-)
-
-const (
- parseObjectKey = iota // parsing object key (before colon)
- parseObjectValue // parsing object value (after colon)
- parseArrayValue // parsing array value
-
- 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.
-
- // This limits the max nesting depth to prevent stack overflow.
- // This is permitted by https://tools.ietf.org/html/rfc7159#section-9
- maxNestingDepth = 10000
-)
-
-type (
- scanner struct {
- step func(*scanner, byte) scanStatus
- parseState []int
- endTop bool
- err error
- index int
- }
-)
-
-var scannerPool = sync.Pool{
- New: func() any {
- return &scanner{}
- },
-}
-
-func newScanner() *scanner {
- s := scannerPool.Get().(*scanner)
- s.reset()
- return s
-}
-
-func freeScanner(s *scanner) {
- // Avoid hanging on to too much memory in extreme cases.
- if len(s.parseState) > 1024 {
- s.parseState = nil
- }
- scannerPool.Put(s)
-}
-
-// 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 := newScanner()
- defer freeScanner(s)
- _ = 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 {
- 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.parseState = s.parseState[0:0]
- s.err = nil
- s.endTop = false
- s.index = 0
-}
-
-// 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
-}
-
-// pushParseState pushes a new parse state p onto the parse stack.
-// an error state is returned if maxNestingDepth was exceeded, otherwise successState is returned.
-func (s *scanner) pushParseState(c byte, newParseState int, successState scanStatus) scanStatus {
- s.parseState = append(s.parseState, newParseState)
- if len(s.parseState) <= maxNestingDepth {
- return successState
- }
- return s.error(c, "exceeded max depth")
-}
-
-// popParseState pops a parse state (already obtained) off the stack
-// and updates s.step accordingly.
-func (s *scanner) popParseState() {
- n := len(s.parseState) - 1
- s.parseState = s.parseState[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
- return s.pushParseState(c, parseObjectKey, scanBeginObject)
- case '[':
- s.step = stateBeginValueOrEmpty
- return s.pushParseState(c, parseArrayValue, 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.parseState)
- s.parseState[n-1] = parseObjectValue
- 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.parseState)
- 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.parseState[n-1]
- switch ps {
- case parseObjectKey:
- if c == ':' {
- s.parseState[n-1] = parseObjectValue
- s.step = stateBeginValue
- return scanObjectKey
- }
- return s.error(c, "after object key")
- case parseObjectValue:
- if c == ',' {
- s.parseState[n-1] = parseObjectKey
- s.step = stateBeginString
- return scanObjectValue
- }
- if c == '}' {
- s.popParseState()
- return scanEndObject
- }
- return s.error(c, "after object key:value pair")
- case parseArrayValue:
- 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/json/parser.go b/vendor/github.com/gabriel-vasile/mimetype/internal/json/parser.go
new file mode 100644
index 00000000..4bc86174
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/json/parser.go
@@ -0,0 +1,478 @@
+package json
+
+import (
+ "bytes"
+ "sync"
+)
+
+const (
+ QueryNone = "json"
+ QueryGeo = "geo"
+ QueryHAR = "har"
+ QueryGLTF = "gltf"
+ maxRecursion = 4096
+)
+
+var queries = map[string][]query{
+ QueryNone: nil,
+ QueryGeo: {{
+ SearchPath: [][]byte{[]byte("type")},
+ SearchVals: [][]byte{
+ []byte(`"Feature"`),
+ []byte(`"FeatureCollection"`),
+ []byte(`"Point"`),
+ []byte(`"LineString"`),
+ []byte(`"Polygon"`),
+ []byte(`"MultiPoint"`),
+ []byte(`"MultiLineString"`),
+ []byte(`"MultiPolygon"`),
+ []byte(`"GeometryCollection"`),
+ },
+ }},
+ QueryHAR: {{
+ SearchPath: [][]byte{[]byte("log"), []byte("version")},
+ }, {
+ SearchPath: [][]byte{[]byte("log"), []byte("creator")},
+ }, {
+ SearchPath: [][]byte{[]byte("log"), []byte("entries")},
+ }},
+ QueryGLTF: {{
+ SearchPath: [][]byte{[]byte("asset"), []byte("version")},
+ SearchVals: [][]byte{[]byte(`"1.0"`), []byte(`"2.0"`)},
+ }},
+}
+
+var parserPool = sync.Pool{
+ New: func() any {
+ return &parserState{maxRecursion: maxRecursion}
+ },
+}
+
+// parserState holds the state of JSON parsing. The number of inspected bytes,
+// the current path inside the JSON object, etc.
+type parserState struct {
+ // ib represents the number of inspected bytes.
+ // Because mimetype limits itself to only reading the header of the file,
+ // it means sometimes the input JSON can be truncated. In that case, we want
+ // to still detect it as JSON, even if it's invalid/truncated.
+ // When ib == len(input) it means the JSON was valid (at least the header).
+ ib int
+ maxRecursion int
+ // currPath keeps a track of the JSON keys parsed up.
+ // It works only for JSON objects. JSON arrays are ignored
+ // mainly because the functionality is not needed.
+ currPath [][]byte
+ // firstToken stores the first JSON token encountered in input.
+ // TODO: performance would be better if we would stop parsing as soon
+ // as we see that first token is not what we are interested in.
+ firstToken int
+ // querySatisfied is true if both path and value of any queries passed to
+ // consumeAny are satisfied.
+ querySatisfied bool
+}
+
+// query holds information about a combination of {"key": "val"} that we're trying
+// to search for inside the JSON.
+type query struct {
+ // SearchPath represents the whole path to look for inside the JSON.
+ // ex: [][]byte{[]byte("foo"), []byte("bar")} matches {"foo": {"bar": "baz"}}
+ SearchPath [][]byte
+ // SearchVals represents values to look for when the SearchPath is found.
+ // Each SearchVal element is tried until one of them matches (logical OR.)
+ SearchVals [][]byte
+}
+
+func eq(path1, path2 [][]byte) bool {
+ if len(path1) != len(path2) {
+ return false
+ }
+ for i := range path1 {
+ if !bytes.Equal(path1[i], path2[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// LooksLikeObjectOrArray reports if first non white space character from raw
+// is either { or [. Parsing raw as JSON is a heavy operation. When receiving some
+// text input we can skip parsing if the input does not even look like JSON.
+func LooksLikeObjectOrArray(raw []byte) bool {
+ for i := range raw {
+ if isSpace(raw[i]) {
+ continue
+ }
+ return raw[i] == '{' || raw[i] == '['
+ }
+
+ return false
+}
+
+// Parse will take out a parser from the pool depending on queryType and tries
+// to parse raw bytes as JSON.
+func Parse(queryType string, raw []byte) (parsed, inspected, firstToken int, querySatisfied bool) {
+ p := parserPool.Get().(*parserState)
+ defer func() {
+ // Avoid hanging on to too much memory in extreme input cases.
+ if len(p.currPath) > 128 {
+ p.currPath = nil
+ }
+ parserPool.Put(p)
+ }()
+ p.reset()
+
+ qs := queries[queryType]
+ got := p.consumeAny(raw, qs, 0)
+ return got, p.ib, p.firstToken, p.querySatisfied
+}
+
+func (p *parserState) reset() {
+ p.ib = 0
+ p.currPath = p.currPath[0:0]
+ p.firstToken = TokInvalid
+ p.querySatisfied = false
+}
+
+func (p *parserState) consumeSpace(b []byte) (n int) {
+ for len(b) > 0 && isSpace(b[0]) {
+ b = b[1:]
+ n++
+ p.ib++
+ }
+ return n
+}
+
+func (p *parserState) consumeConst(b, cnst []byte) int {
+ lb := len(b)
+ for i, c := range cnst {
+ if lb > i && b[i] == c {
+ p.ib++
+ } else {
+ return 0
+ }
+ }
+ return len(cnst)
+}
+
+func (p *parserState) consumeString(b []byte) (n int) {
+ var c byte
+ for len(b[n:]) > 0 {
+ c, n = b[n], n+1
+ p.ib++
+ switch c {
+ case '\\':
+ if len(b[n:]) == 0 {
+ return 0
+ }
+ switch b[n] {
+ case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
+ n++
+ p.ib++
+ continue
+ case 'u':
+ n++
+ p.ib++
+ for j := 0; j < 4 && len(b[n:]) > 0; j++ {
+ if !isXDigit(b[n]) {
+ return 0
+ }
+ n++
+ p.ib++
+ }
+ continue
+ default:
+ return 0
+ }
+ case '"':
+ return n
+ default:
+ continue
+ }
+ }
+ return 0
+}
+
+func (p *parserState) consumeNumber(b []byte) (n int) {
+ got := false
+ var i int
+
+ if len(b) == 0 {
+ goto out
+ }
+ if b[0] == '-' {
+ b, i = b[1:], i+1
+ p.ib++
+ }
+
+ for len(b) > 0 {
+ if !isDigit(b[0]) {
+ break
+ }
+ got = true
+ b, i = b[1:], i+1
+ p.ib++
+ }
+ if len(b) == 0 {
+ goto out
+ }
+ if b[0] == '.' {
+ b, i = b[1:], i+1
+ p.ib++
+ }
+ for len(b) > 0 {
+ if !isDigit(b[0]) {
+ break
+ }
+ got = true
+ b, i = b[1:], i+1
+ p.ib++
+ }
+ if len(b) == 0 {
+ goto out
+ }
+ if got && (b[0] == 'e' || b[0] == 'E') {
+ b, i = b[1:], i+1
+ p.ib++
+ got = false
+ if len(b) == 0 {
+ goto out
+ }
+ if b[0] == '+' || b[0] == '-' {
+ b, i = b[1:], i+1
+ p.ib++
+ }
+ for len(b) > 0 {
+ if !isDigit(b[0]) {
+ break
+ }
+ got = true
+ b, i = b[1:], i+1
+ p.ib++
+ }
+ }
+out:
+ if got {
+ return i
+ }
+ return 0
+}
+
+func (p *parserState) consumeArray(b []byte, qs []query, lvl int) (n int) {
+ p.appendPath([]byte{'['}, qs)
+ if len(b) == 0 {
+ return 0
+ }
+
+ for n < len(b) {
+ n += p.consumeSpace(b[n:])
+ if len(b[n:]) == 0 {
+ return 0
+ }
+ if b[n] == ']' {
+ p.ib++
+ p.popLastPath(qs)
+ return n + 1
+ }
+ innerParsed := p.consumeAny(b[n:], qs, lvl)
+ if innerParsed == 0 {
+ return 0
+ }
+ n += innerParsed
+ if len(b[n:]) == 0 {
+ return 0
+ }
+ switch b[n] {
+ case ',':
+ n += 1
+ p.ib++
+ continue
+ case ']':
+ p.ib++
+ return n + 1
+ default:
+ return 0
+ }
+ }
+ return 0
+}
+
+func queryPathMatch(qs []query, path [][]byte) int {
+ for i := range qs {
+ if eq(qs[i].SearchPath, path) {
+ return i
+ }
+ }
+ return -1
+}
+
+// appendPath will append a path fragment if queries is not empty.
+// If we don't need query functionality (just checking if a JSON is valid),
+// then we can skip keeping track of the path we're currently in.
+func (p *parserState) appendPath(path []byte, qs []query) {
+ if len(qs) != 0 {
+ p.currPath = append(p.currPath, path)
+ }
+}
+func (p *parserState) popLastPath(qs []query) {
+ if len(qs) != 0 {
+ p.currPath = p.currPath[:len(p.currPath)-1]
+ }
+}
+
+func (p *parserState) consumeObject(b []byte, qs []query, lvl int) (n int) {
+ for n < len(b) {
+ n += p.consumeSpace(b[n:])
+ if len(b[n:]) == 0 {
+ return 0
+ }
+ if b[n] == '}' {
+ p.ib++
+ return n + 1
+ }
+ if b[n] != '"' {
+ return 0
+ } else {
+ n += 1
+ p.ib++
+ }
+ // queryMatched stores the index of the query satisfying the current path.
+ queryMatched := -1
+ if keyLen := p.consumeString(b[n:]); keyLen == 0 {
+ return 0
+ } else {
+ p.appendPath(b[n:n+keyLen-1], qs)
+ if !p.querySatisfied {
+ queryMatched = queryPathMatch(qs, p.currPath)
+ }
+ n += keyLen
+ }
+ n += p.consumeSpace(b[n:])
+ if len(b[n:]) == 0 {
+ return 0
+ }
+ if b[n] != ':' {
+ return 0
+ } else {
+ n += 1
+ p.ib++
+ }
+ n += p.consumeSpace(b[n:])
+ if len(b[n:]) == 0 {
+ return 0
+ }
+
+ if valLen := p.consumeAny(b[n:], qs, lvl); valLen == 0 {
+ return 0
+ } else {
+ if queryMatched != -1 {
+ q := qs[queryMatched]
+ if len(q.SearchVals) == 0 {
+ p.querySatisfied = true
+ }
+ for _, val := range q.SearchVals {
+ if bytes.Equal(val, bytes.TrimSpace(b[n:n+valLen])) {
+ p.querySatisfied = true
+ }
+ }
+ }
+ n += valLen
+ }
+ if len(b[n:]) == 0 {
+ return 0
+ }
+ switch b[n] {
+ case ',':
+ p.popLastPath(qs)
+ n++
+ p.ib++
+ continue
+ case '}':
+ p.popLastPath(qs)
+ p.ib++
+ return n + 1
+ default:
+ return 0
+ }
+ }
+ return 0
+}
+
+func (p *parserState) consumeAny(b []byte, qs []query, lvl int) (n int) {
+ // Avoid too much recursion.
+ if p.maxRecursion != 0 && lvl > p.maxRecursion {
+ return 0
+ }
+ if len(qs) == 0 {
+ p.querySatisfied = true
+ }
+ n += p.consumeSpace(b)
+ if len(b[n:]) == 0 {
+ return 0
+ }
+
+ var t, rv int
+ switch b[n] {
+ case '"':
+ n++
+ p.ib++
+ rv = p.consumeString(b[n:])
+ t = TokString
+ case '[':
+ n++
+ p.ib++
+ rv = p.consumeArray(b[n:], qs, lvl+1)
+ t = TokArray
+ case '{':
+ n++
+ p.ib++
+ rv = p.consumeObject(b[n:], qs, lvl+1)
+ t = TokObject
+ case 't':
+ rv = p.consumeConst(b[n:], []byte("true"))
+ t = TokTrue
+ case 'f':
+ rv = p.consumeConst(b[n:], []byte("false"))
+ t = TokFalse
+ case 'n':
+ rv = p.consumeConst(b[n:], []byte("null"))
+ t = TokNull
+ default:
+ rv = p.consumeNumber(b[n:])
+ t = TokNumber
+ }
+ if lvl == 0 {
+ p.firstToken = t
+ }
+ if rv <= 0 {
+ return n
+ }
+ n += rv
+ n += p.consumeSpace(b[n:])
+ return n
+}
+
+func isSpace(c byte) bool {
+ return c == ' ' || c == '\t' || c == '\r' || c == '\n'
+}
+func isDigit(c byte) bool {
+ return '0' <= c && c <= '9'
+}
+
+func isXDigit(c byte) bool {
+ if isDigit(c) {
+ return true
+ }
+ return ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
+}
+
+const (
+ TokInvalid = 0
+ TokNull = 1 << iota
+ TokTrue
+ TokFalse
+ TokNumber
+ TokString
+ TokArray
+ TokObject
+ TokComma
+)
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/archive.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/archive.go
index 068d00f7..dd7f2417 100644
--- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/archive.go
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/archive.go
@@ -137,7 +137,7 @@ func tarParseOctal(b []byte) int64 {
if b == 0 {
break
}
- if !(b >= '0' && b <= '7') {
+ if b < '0' || b > '7' {
return -1
}
ret = (ret << 3) | int64(b-'0')
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/binary.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/binary.go
index 76973201..70599b34 100644
--- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/binary.go
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/binary.go
@@ -71,7 +71,7 @@ func Dbf(raw []byte, limit uint32) bool {
}
// 3rd and 4th bytes contain the last update month and day of month.
- if !(0 < raw[2] && raw[2] < 13 && 0 < raw[3] && raw[3] < 32) {
+ if raw[2] == 0 || raw[2] > 12 || raw[3] == 0 || raw[3] > 31 {
return false
}
@@ -153,7 +153,7 @@ func Marc(raw []byte, limit uint32) bool {
return bytes.Contains(raw[:min(2048, len(raw))], []byte{0x1E})
}
-// Glb matches a glTF model format file.
+// GLB matches a glTF model format file.
// GLB is the binary file format representation of 3D models saved in
// the GL transmission Format (glTF).
// GLB uses little endian and its header structure is as follows:
@@ -168,7 +168,7 @@ func Marc(raw []byte, limit uint32) bool {
//
// [glTF specification]: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html
// [IANA glTF entry]: https://www.iana.org/assignments/media-types/model/gltf-binary
-var Glb = prefix([]byte("\x67\x6C\x54\x46\x02\x00\x00\x00"),
+var GLB = prefix([]byte("\x67\x6C\x54\x46\x02\x00\x00\x00"),
[]byte("\x67\x6C\x54\x46\x01\x00\x00\x00"))
// TzIf matches a Time Zone Information Format (TZif) file.
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/document.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/document.go
index b3b26d5a..7f9308db 100644
--- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/document.go
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/document.go
@@ -1,18 +1,11 @@
package magic
-import "bytes"
+import (
+ "bytes"
+ "encoding/binary"
+)
var (
- // Pdf matches a Portable Document Format file.
- // https://github.com/file/file/blob/11010cc805546a3e35597e67e1129a481aed40e8/magic/Magdir/pdf
- Pdf = prefix(
- // usual pdf signature
- []byte("%PDF-"),
- // new-line prefixed signature
- []byte("\012%PDF-"),
- // UTF-8 BOM prefixed signature
- []byte("\xef\xbb\xbf%PDF-"),
- )
// Fdf matches a Forms Data Format file.
Fdf = prefix([]byte("%FDF"))
// Mobi matches a Mobi file.
@@ -21,8 +14,18 @@ var (
Lit = prefix([]byte("ITOLITLS"))
)
+// PDF matches a Portable Document Format file.
+// The %PDF- header should be the first thing inside the file but many
+// implementations don't follow the rule. The PDF spec at Appendix H says the
+// signature can be prepended by anything.
+// https://bugs.astron.com/view.php?id=446
+func PDF(raw []byte, _ uint32) bool {
+ raw = raw[:min(len(raw), 1024)]
+ return bytes.Contains(raw, []byte("%PDF-"))
+}
+
// DjVu matches a DjVu file.
-func DjVu(raw []byte, limit uint32) bool {
+func DjVu(raw []byte, _ uint32) bool {
if len(raw) < 12 {
return false
}
@@ -36,7 +39,7 @@ func DjVu(raw []byte, limit uint32) bool {
}
// P7s matches an .p7s signature File (PEM, Base64).
-func P7s(raw []byte, limit uint32) bool {
+func P7s(raw []byte, _ uint32) bool {
// Check for PEM Encoding.
if bytes.HasPrefix(raw, []byte("-----BEGIN PKCS7")) {
return true
@@ -60,3 +63,21 @@ func P7s(raw []byte, limit uint32) bool {
return false
}
+
+// Lotus123 matches a Lotus 1-2-3 spreadsheet document.
+func Lotus123(raw []byte, _ uint32) bool {
+ if len(raw) <= 20 {
+ return false
+ }
+ version := binary.BigEndian.Uint32(raw)
+ if version == 0x00000200 {
+ return raw[6] != 0 && raw[7] == 0
+ }
+
+ return version == 0x00001a00 && raw[20] > 0 && raw[20] < 32
+}
+
+// CHM matches a Microsoft Compiled HTML Help file.
+func CHM(raw []byte, _ uint32) bool {
+ return bytes.HasPrefix(raw, []byte("ITSF\003\000\000\000\x60\000\000\000"))
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/geo.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/geo.go
index f077e167..cade91f1 100644
--- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/geo.go
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/geo.go
@@ -12,13 +12,13 @@ func Shp(raw []byte, limit uint32) bool {
return false
}
- if !(binary.BigEndian.Uint32(raw[0:4]) == 9994 &&
- binary.BigEndian.Uint32(raw[4:8]) == 0 &&
- binary.BigEndian.Uint32(raw[8:12]) == 0 &&
- binary.BigEndian.Uint32(raw[12:16]) == 0 &&
- binary.BigEndian.Uint32(raw[16:20]) == 0 &&
- binary.BigEndian.Uint32(raw[20:24]) == 0 &&
- binary.LittleEndian.Uint32(raw[28:32]) == 1000) {
+ if binary.BigEndian.Uint32(raw[0:4]) != 9994 ||
+ binary.BigEndian.Uint32(raw[4:8]) != 0 ||
+ binary.BigEndian.Uint32(raw[8:12]) != 0 ||
+ binary.BigEndian.Uint32(raw[12:16]) != 0 ||
+ binary.BigEndian.Uint32(raw[16:20]) != 0 ||
+ binary.BigEndian.Uint32(raw[20:24]) != 0 ||
+ binary.LittleEndian.Uint32(raw[28:32]) != 1000 {
return false
}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go
index a34c6098..5fe435b9 100644
--- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go
@@ -4,6 +4,8 @@ package magic
import (
"bytes"
"fmt"
+
+ "github.com/gabriel-vasile/mimetype/internal/scan"
)
type (
@@ -74,12 +76,13 @@ func ciCheck(sig, raw []byte) bool {
// matches the raw input.
func xml(sigs ...xmlSig) Detector {
return func(raw []byte, limit uint32) bool {
- raw = trimLWS(raw)
- if len(raw) == 0 {
+ b := scan.Bytes(raw)
+ b.TrimLWS()
+ if len(b) == 0 {
return false
}
for _, s := range sigs {
- if xmlCheck(s, raw) {
+ if xmlCheck(s, b) {
return true
}
}
@@ -104,19 +107,19 @@ func xmlCheck(sig xmlSig, raw []byte) bool {
// matches the raw input.
func markup(sigs ...[]byte) Detector {
return func(raw []byte, limit uint32) bool {
- if bytes.HasPrefix(raw, []byte{0xEF, 0xBB, 0xBF}) {
+ b := scan.Bytes(raw)
+ if bytes.HasPrefix(b, []byte{0xEF, 0xBB, 0xBF}) {
// We skip the UTF-8 BOM if present to ensure we correctly
// process any leading whitespace. The presence of the BOM
// is taken into account during charset detection in charset.go.
- raw = trimLWS(raw[3:])
- } else {
- raw = trimLWS(raw)
+ b.Advance(3)
}
- if len(raw) == 0 {
+ b.TrimLWS()
+ if len(b) == 0 {
return false
}
for _, s := range sigs {
- if markupCheck(s, raw) {
+ if markupCheck(s, b) {
return true
}
}
@@ -139,7 +142,7 @@ func markupCheck(sig, raw []byte) bool {
}
}
// Next byte must be space or right angle bracket.
- if db := raw[len(sig)]; db != ' ' && db != '>' {
+ if db := raw[len(sig)]; !scan.ByteIsWS(db) && db != '>' {
return false
}
@@ -183,8 +186,10 @@ func newXMLSig(localName, xmlns string) xmlSig {
// /usr/bin/env is the interpreter, php is the first and only argument.
func shebang(sigs ...[]byte) Detector {
return func(raw []byte, limit uint32) bool {
+ b := scan.Bytes(raw)
+ line := b.Line()
for _, s := range sigs {
- if shebangCheck(s, firstLine(raw)) {
+ if shebangCheck(s, line) {
return true
}
}
@@ -192,7 +197,7 @@ func shebang(sigs ...[]byte) Detector {
}
}
-func shebangCheck(sig, raw []byte) bool {
+func shebangCheck(sig []byte, raw scan.Bytes) bool {
if len(raw) < len(sig)+2 {
return false
}
@@ -200,52 +205,8 @@ func shebangCheck(sig, raw []byte) bool {
return false
}
- return bytes.Equal(trimLWS(trimRWS(raw[2:])), sig)
-}
-
-// 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 == ' '
-}
-
-func min(a, b int) int {
- if a < b {
- return a
- }
- return b
-}
-
-type readBuf []byte
-
-func (b *readBuf) advance(n int) bool {
- if n < 0 || len(*b) < n {
- return false
- }
- *b = (*b)[n:]
- return true
+ raw.Advance(2) // skip #! we checked above
+ raw.TrimLWS()
+ raw.TrimRWS()
+ return bytes.Equal(raw, sig)
}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go
index 7d60e22e..c912823e 100644
--- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go
@@ -7,17 +7,34 @@ import (
// Xlsx matches a Microsoft Excel 2007 file.
func Xlsx(raw []byte, limit uint32) bool {
- return zipContains(raw, []byte("xl/"), true)
+ return msoxml(raw, zipEntries{{
+ name: []byte("xl/"),
+ dir: true,
+ }}, 100)
}
// Docx matches a Microsoft Word 2007 file.
func Docx(raw []byte, limit uint32) bool {
- return zipContains(raw, []byte("word/"), true)
+ return msoxml(raw, zipEntries{{
+ name: []byte("word/"),
+ dir: true,
+ }}, 100)
}
// Pptx matches a Microsoft PowerPoint 2007 file.
func Pptx(raw []byte, limit uint32) bool {
- return zipContains(raw, []byte("ppt/"), true)
+ return msoxml(raw, zipEntries{{
+ name: []byte("ppt/"),
+ dir: true,
+ }}, 100)
+}
+
+// Visio matches a Microsoft Visio 2013+ file.
+func Visio(raw []byte, limit uint32) bool {
+ return msoxml(raw, zipEntries{{
+ name: []byte("visio/"),
+ dir: true,
+ }}, 100)
}
// Ole matches an Open Linking and Embedding file.
@@ -157,6 +174,14 @@ func Msi(raw []byte, limit uint32) bool {
})
}
+// One matches a Microsoft OneNote file.
+func One(raw []byte, limit uint32) bool {
+ return bytes.HasPrefix(raw, []byte{
+ 0xe4, 0x52, 0x5c, 0x7b, 0x8c, 0xd8, 0xa7, 0x4d,
+ 0xae, 0xb1, 0x53, 0x78, 0xd0, 0x29, 0x96, 0xd3,
+ })
+}
+
// Helper to match by a specific CLSID of a compound file.
//
// http://fileformats.archiveteam.org/wiki/Microsoft_Compound_File
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/netpbm.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/netpbm.go
new file mode 100644
index 00000000..4baa2576
--- /dev/null
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/netpbm.go
@@ -0,0 +1,111 @@
+package magic
+
+import (
+ "bytes"
+ "strconv"
+
+ "github.com/gabriel-vasile/mimetype/internal/scan"
+)
+
+// NetPBM matches a Netpbm Portable BitMap ASCII/Binary file.
+//
+// See: https://en.wikipedia.org/wiki/Netpbm
+func NetPBM(raw []byte, _ uint32) bool {
+ return netp(raw, "P1\n", "P4\n")
+}
+
+// NetPGM matches a Netpbm Portable GrayMap ASCII/Binary file.
+//
+// See: https://en.wikipedia.org/wiki/Netpbm
+func NetPGM(raw []byte, _ uint32) bool {
+ return netp(raw, "P2\n", "P5\n")
+}
+
+// NetPPM matches a Netpbm Portable PixMap ASCII/Binary file.
+//
+// See: https://en.wikipedia.org/wiki/Netpbm
+func NetPPM(raw []byte, _ uint32) bool {
+ return netp(raw, "P3\n", "P6\n")
+}
+
+// NetPAM matches a Netpbm Portable Arbitrary Map file.
+//
+// See: https://en.wikipedia.org/wiki/Netpbm
+func NetPAM(raw []byte, _ uint32) bool {
+ if !bytes.HasPrefix(raw, []byte("P7\n")) {
+ return false
+ }
+ w, h, d, m, e := false, false, false, false, false
+ s := scan.Bytes(raw)
+ var l scan.Bytes
+ // Read line by line.
+ for i := 0; i < 128; i++ {
+ l = s.Line()
+ // If the line is empty or a comment, skip.
+ if len(l) == 0 || l.Peek() == '#' {
+ if len(s) == 0 {
+ return false
+ }
+ continue
+ } else if bytes.HasPrefix(l, []byte("TUPLTYPE")) {
+ continue
+ } else if bytes.HasPrefix(l, []byte("WIDTH ")) {
+ w = true
+ } else if bytes.HasPrefix(l, []byte("HEIGHT ")) {
+ h = true
+ } else if bytes.HasPrefix(l, []byte("DEPTH ")) {
+ d = true
+ } else if bytes.HasPrefix(l, []byte("MAXVAL ")) {
+ m = true
+ } else if bytes.HasPrefix(l, []byte("ENDHDR")) {
+ e = true
+ }
+ // When we reached header, return true if we collected all four required headers.
+ // WIDTH, HEIGHT, DEPTH and MAXVAL.
+ if e {
+ return w && h && d && m
+ }
+ }
+ return false
+}
+
+func netp(s scan.Bytes, prefixes ...string) bool {
+ foundPrefix := ""
+ for _, p := range prefixes {
+ if bytes.HasPrefix(s, []byte(p)) {
+ foundPrefix = p
+ }
+ }
+ if foundPrefix == "" {
+ return false
+ }
+ s.Advance(len(foundPrefix)) // jump over P1, P2, P3, etc.
+
+ var l scan.Bytes
+ // Read line by line.
+ for i := 0; i < 128; i++ {
+ l = s.Line()
+ // If the line is a comment, skip.
+ if l.Peek() == '#' {
+ continue
+ }
+ // If line has leading whitespace, then skip over whitespace.
+ for scan.ByteIsWS(l.Peek()) {
+ l.Advance(1)
+ }
+ if len(s) == 0 || len(l) > 0 {
+ break
+ }
+ }
+
+ // At this point l should be the two integers denoting the size of the matrix.
+ width := l.PopUntil(scan.ASCIISpaces...)
+ for scan.ByteIsWS(l.Peek()) {
+ l.Advance(1)
+ }
+ height := l.PopUntil(scan.ASCIISpaces...)
+
+ w, errw := strconv.ParseInt(string(width), 10, 64)
+ h, errh := strconv.ParseInt(string(height), 10, 64)
+ return errw == nil && errh == nil && w > 0 && h > 0
+}
diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go
index cf644639..1841ee87 100644
--- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go
+++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go
@@ -2,11 +2,12 @@ package magic
import (
"bytes"
- "strings"
"time"
"github.com/gabriel-vasile/mimetype/internal/charset"
"github.com/gabriel-vasile/mimetype/internal/json"
+ mkup "github.com/gabriel-vasile/mimetype/internal/markup"
+ "github.com/gabriel-vasile/mimetype/internal/scan"
)
var (
@@ -28,6 +29,7 @@ var (
[]byte("