1 // Package mimetype uses magic number signatures to detect the MIME type of a file. 2 // 3 // File formats are stored in a hierarchy with application/octet-stream at its root. 4 // For example, the hierarchy for HTML format is application/octet-stream -> 5 // text/plain -> text/html. 6 package mimetype 7 8 import ( 9 "io" 10 "io/ioutil" 11 "mime" 12 "os" 13 "sync/atomic" 14 ) 15 16 // readLimit is the maximum number of bytes from the input used when detecting. 17 var readLimit uint32 = 3072 18 19 // Detect returns the MIME type found from the provided byte slice. 20 // 21 // The result is always a valid MIME type, with application/octet-stream 22 // returned when identification failed. 23 func Detect(in []byte) *MIME { 24 // Using atomic because readLimit can be written at the same time in other goroutine. 25 l := atomic.LoadUint32(&readLimit) 26 if l > 0 && len(in) > int(l) { 27 in = in[:l] 28 } 29 mu.RLock() 30 defer mu.RUnlock() 31 return root.match(in, l) 32 } 33 34 // DetectReader returns the MIME type of the provided reader. 35 // 36 // The result is always a valid MIME type, with application/octet-stream 37 // returned when identification failed with or without an error. 38 // Any error returned is related to the reading from the input reader. 39 // 40 // DetectReader assumes the reader offset is at the start. If the input is an 41 // io.ReadSeeker you previously read from, it should be rewinded before detection: 42 // 43 // reader.Seek(0, io.SeekStart) 44 func DetectReader(r io.Reader) (*MIME, error) { 45 var in []byte 46 var err error 47 48 // Using atomic because readLimit can be written at the same time in other goroutine. 49 l := atomic.LoadUint32(&readLimit) 50 if l == 0 { 51 in, err = ioutil.ReadAll(r) 52 if err != nil { 53 return errMIME, err 54 } 55 } else { 56 var n int 57 in = make([]byte, l) 58 // io.UnexpectedEOF means len(r) < len(in). It is not an error in this case, 59 // it just means the input file is smaller than the allocated bytes slice. 60 n, err = io.ReadFull(r, in) 61 if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { 62 return errMIME, err 63 } 64 in = in[:n] 65 } 66 67 mu.RLock() 68 defer mu.RUnlock() 69 return root.match(in, l), nil 70 } 71 72 // DetectFile returns the MIME type of the provided file. 73 // 74 // The result is always a valid MIME type, with application/octet-stream 75 // returned when identification failed with or without an error. 76 // Any error returned is related to the opening and reading from the input file. 77 func DetectFile(path string) (*MIME, error) { 78 f, err := os.Open(path) 79 if err != nil { 80 return errMIME, err 81 } 82 defer f.Close() 83 84 return DetectReader(f) 85 } 86 87 // EqualsAny reports whether s MIME type is equal to any MIME type in mimes. 88 // MIME type equality test is done on the "type/subtype" section, ignores 89 // any optional MIME parameters, ignores any leading and trailing whitespace, 90 // and is case insensitive. 91 func EqualsAny(s string, mimes ...string) bool { 92 s, _, _ = mime.ParseMediaType(s) 93 for _, m := range mimes { 94 m, _, _ = mime.ParseMediaType(m) 95 if s == m { 96 return true 97 } 98 } 99 100 return false 101 } 102 103 // SetLimit sets the maximum number of bytes read from input when detecting the MIME type. 104 // Increasing the limit provides better detection for file formats which store 105 // their magical numbers towards the end of the file: docx, pptx, xlsx, etc. 106 // A limit of 0 means the whole input file will be used. 107 func SetLimit(limit uint32) { 108 // Using atomic because readLimit can be read at the same time in other goroutine. 109 atomic.StoreUint32(&readLimit, limit) 110 } 111 112 // Extend adds detection for other file formats. 113 // It is equivalent to calling Extend() on the root mime type "application/octet-stream". 114 func Extend(detector func(raw []byte, limit uint32) bool, mime, extension string, aliases ...string) { 115 root.Extend(detector, mime, extension, aliases...) 116 } 117 118 // Lookup finds a MIME object by its string representation. 119 // The representation can be the main mime type, or any of its aliases. 120 func Lookup(mime string) *MIME { 121 mu.RLock() 122 defer mu.RUnlock() 123 return root.lookup(mime) 124 } 125