99 "fmt"
1010 "io"
1111 "net/http"
12+ "path/filepath"
1213 "regexp"
1314 "slices"
1415 "strings"
@@ -26,6 +27,14 @@ const (
2627 MimeTypeApplicationOctetStream = "application/octet-stream"
2728)
2829
30+ // list of supported CAD 3D file extensions
31+ var supportedCAD3DFileExtensions = []string {
32+ ".3dm" , ".3ds" , ".3mf" , ".amf" , ".bim" , ".brep" ,
33+ ".dae" , ".fbx" , ".fcstd" , ".glb" , ".gltf" ,
34+ ".ifc" , ".igs" , ".iges" , ".stp" , ".step" ,
35+ ".stl" , ".obj" , ".off" , ".ply" , ".wrl" ,
36+ }
37+
2938var (
3039 svgComment = regexp .MustCompile (`(?s)<!--.*?-->` )
3140 svgTagRegex = regexp .MustCompile (`(?si)\A\s*(?:(<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg\b` )
3544// SniffedType contains information about a blobs type.
3645type SniffedType struct {
3746 contentType string
47+ fileName string
3848}
3949
4050// IsText etects if content format is plain text.
@@ -67,6 +77,20 @@ func (ct SniffedType) IsAudio() bool {
6777 return strings .Contains (ct .contentType , "audio/" )
6878}
6979
80+ // Is3DFile detects if data is a 3D model file format
81+ func (ct SniffedType ) Is3DFile () bool {
82+ if ct .fileName == "" {
83+ return false
84+ }
85+ ext := strings .ToLower (filepath .Ext (ct .fileName ))
86+ for _ , supportedExt := range supportedCAD3DFileExtensions {
87+ if ext == supportedExt {
88+ return true
89+ }
90+ }
91+ return false
92+ }
93+
7094// IsRepresentableAsText returns true if file content can be represented as
7195// plain text or is empty.
7296func (ct SniffedType ) IsRepresentableAsText () bool {
@@ -75,7 +99,7 @@ func (ct SniffedType) IsRepresentableAsText() bool {
7599
76100// IsBrowsableBinaryType returns whether a non-text type can be displayed in a browser
77101func (ct SniffedType ) IsBrowsableBinaryType () bool {
78- return ct .IsImage () || ct .IsSvgImage () || ct .IsPDF () || ct .IsVideo () || ct .IsAudio ()
102+ return ct .IsImage () || ct .IsSvgImage () || ct .IsPDF () || ct .IsVideo () || ct .IsAudio () || ct . Is3DFile ()
79103}
80104
81105// GetMimeType returns the mime type
@@ -105,8 +129,13 @@ func detectFileTypeBox(data []byte) (brands []string, found bool) {
105129
106130// DetectContentType extends http.DetectContentType with more content types. Defaults to text/unknown if input is empty.
107131func DetectContentType (data []byte ) SniffedType {
132+ return DetectContentTypeWithFilename (data , "" )
133+ }
134+
135+ // DetectContentTypeWithFilename extends http.DetectContentType with more content types and uses filename for additional detection. Defaults to text/unknown if input is empty.
136+ func DetectContentTypeWithFilename (data []byte , fileName string ) SniffedType {
108137 if len (data ) == 0 {
109- return SniffedType {"text/unknown" }
138+ return SniffedType {"text/unknown" , fileName }
110139 }
111140
112141 ct := http .DetectContentType (data )
@@ -153,17 +182,22 @@ func DetectContentType(data []byte) SniffedType {
153182 ct = "audio/ogg" // for most cases, it is used as an audio container
154183 }
155184 }
156- return SniffedType {ct }
185+ return SniffedType {ct , fileName }
157186}
158187
159188// DetectContentTypeFromReader guesses the content type contained in the reader.
160189func DetectContentTypeFromReader (r io.Reader ) (SniffedType , error ) {
190+ return DetectContentTypeFromReaderWithFilename (r , "" )
191+ }
192+
193+ // DetectContentTypeFromReaderWithFilename guesses the content type contained in the reader with given filename.
194+ func DetectContentTypeFromReaderWithFilename (r io.Reader , fileName string ) (SniffedType , error ) {
161195 buf := make ([]byte , sniffLen )
162196 n , err := util .ReadAtMost (r , buf )
163197 if err != nil {
164198 return SniffedType {}, fmt .Errorf ("DetectContentTypeFromReader io error: %w" , err )
165199 }
166200 buf = buf [:n ]
167201
168- return DetectContentType (buf ), nil
202+ return DetectContentTypeWithFilename (buf , fileName ), nil
169203}
0 commit comments