// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package http import ( "bytes" "encoding/binary" ) // The algorithm uses at most sniffLen bytes to make its decision. const sniffLen = 512 // DetectContentType implements the algorithm described // at https://mimesniff.spec.whatwg.org/ to determine the // Content-Type of the given data. It considers at most the // first 512 bytes of data. DetectContentType always returns // a valid MIME type: if it cannot determine a more specific one, it // returns "application/octet-stream". func DetectContentType(data []byte) string { if len(data) > sniffLen { data = data[:sniffLen] } // Index of the first non-whitespace byte in data. firstNonWS := 0 for ; firstNonWS < len(data) && isWS(data[firstNonWS]); firstNonWS++ { } for _, sig := range sniffSignatures { if ct := sig.match(data, firstNonWS); ct != "" { return ct } } return "application/octet-stream" // fallback } func isWS(b byte) bool { switch b { case '\t', '\n', '\x0c', '\r', ' ': return true } return false } type sniffSig interface { // match returns the MIME type of the data, or "" if unknown. match(data []byte, firstNonWS int) string } // Data matching the table in section 6. var sniffSignatures = []sniffSig{ htmlSig("' { return "" } return "text/html; charset=utf-8" } var mp4ftype = []byte("ftyp") var mp4 = []byte("mp4") type mp4Sig struct{} func (mp4Sig) match(data []byte, firstNonWS int) string { // https://mimesniff.spec.whatwg.org/#signature-for-mp4 // c.f. section 6.2.1 if len(data) < 12 { return "" } boxSize := int(binary.BigEndian.Uint32(data[:4])) if boxSize%4 != 0 || len(data) < boxSize { return "" } if !bytes.Equal(data[4:8], mp4ftype) { return "" } for st := 8; st < boxSize; st += 4 { if st == 12 { // minor version number continue } if bytes.Equal(data[st:st+3], mp4) { return "video/mp4" } } return "" } type textSig struct{} func (textSig) match(data []byte, firstNonWS int) string { // c.f. section 5, step 4. for _, b := range data[firstNonWS:] { switch { case b <= 0x08, b == 0x0B, 0x0E <= b && b <= 0x1A, 0x1C <= b && b <= 0x1F: return "" } } return "text/plain; charset=utf-8" }