Skip to content
quarry Artifact [fb2ed745a6]
Login

Artifact [fb2ed745a6]

Artifact fb2ed745a60dfac2fb9349f5c39c6a4bc7aa1af3f8614cf4361439fa9771a841:


// Package sniff decides what an uploaded file IS from its first bytes
// and nothing else (RFC 0016 §Reading, §Inline).
//
// An extension is a name the uploader chose and a mimetype is a card
// they wrote; neither is a fact about the content. Every route that
// answers with a content type a browser will render reads this package
// instead, and the same answer is what the attachment card draws, so
// the preview a reader sees and the bytes the browser gets can never
// disagree about what the file is.
package sniff

import (
	"bytes"
	"unicode/utf8"
)

// The four kinds an attachment card draws.
const (
	KindImage = "image"
	KindText  = "text"
	KindMedia = "media"
	KindOther = "other"
)

// Image reports the content type of bytes that BEGIN with one of the
// four raster signatures quarry renders inline, and false for anything
// else. SVG cannot reach it by construction: SVG is script, and no
// magic number admits it.
func Image(raw []byte) (string, bool) {
	switch {
	case bytes.HasPrefix(raw, []byte("\x89PNG\r\n\x1a\n")):
		return "image/png", true
	case bytes.HasPrefix(raw, []byte{0xff, 0xd8, 0xff}):
		return "image/jpeg", true
	case bytes.HasPrefix(raw, []byte("GIF87a")), bytes.HasPrefix(raw, []byte("GIF89a")):
		return "image/gif", true
	case riff(raw, "WEBP"):
		return "image/webp", true
	}
	return "", false
}

// Media reports the content type of bytes that begin with one of the
// audio or video signatures quarry serves inline, and false otherwise.
//
// These types are handed to a <video> or <audio> element, which decodes
// them with the same codecs an image goes through: a container the
// browser cannot parse plays nothing, and no container here can become
// a document. PDF and SVG stay off the list for the opposite reason —
// both are rendered by an engine that runs script.
func Media(raw []byte) (string, bool) {
	switch {
	case len(raw) >= 8 && bytes.Equal(raw[4:8], []byte("ftyp")):
		return "video/mp4", true
	case bytes.HasPrefix(raw, []byte{0x1a, 0x45, 0xdf, 0xa3}):
		return "video/webm", true
	case bytes.HasPrefix(raw, []byte("OggS")):
		// The container says nothing about whether the stream inside is
		// audio or video, so the audio type is the safe claim: every
		// browser that plays Ogg at all plays it in an <audio>.
		return "audio/ogg", true
	case bytes.HasPrefix(raw, []byte("ID3")):
		return "audio/mpeg", true
	case len(raw) >= 2 && raw[0] == 0xff && raw[1]&0xe0 == 0xe0:
		return "audio/mpeg", true
	case riff(raw, "WAVE"):
		return "audio/wav", true
	}
	return "", false
}

// riff reports whether raw is a RIFF container of the named form. WAV
// and WebP share the same first four bytes, so the form at offset 8 is
// the only thing that separates a sound from a picture.
func riff(raw []byte, form string) bool {
	return len(raw) >= 12 && bytes.HasPrefix(raw, []byte("RIFF")) &&
		bytes.Equal(raw[8:12], []byte(form))
}

// Text reports whether these bytes can be shown as text: valid UTF-8
// with no NUL anywhere in them. The whole slice is scanned rather than
// a prefix, because a file that reads as prose for a screen and then
// turns binary would otherwise hand a terminal's worth of escape
// sequences to a reader who asked for a preview.
func Text(raw []byte) bool {
	if len(raw) == 0 {
		return false
	}
	return utf8.Valid(raw) && !bytes.ContainsRune(raw, 0)
}

// Kind classifies an attachment into the four shapes a card draws.
// Order matters: an image is an image before it is anything else, and
// text is the last claim made, so a container that merely happens to
// be valid UTF-8 is still media.
func Kind(raw []byte) string {
	if _, ok := Image(raw); ok {
		return KindImage
	}
	if _, ok := Media(raw); ok {
		return KindMedia
	}
	if Text(raw) {
		return KindText
	}
	return KindOther
}

// TrimPartialRune drops an incomplete UTF-8 sequence from the end of a
// PREFIX of a larger file.
//
// A bounded read cuts wherever the bound falls, which is routinely in
// the middle of a multibyte rune: without this, every UTF-8 document
// whose 65,536th byte lands mid-character would be judged binary. At
// most three bytes go, and only bytes that are a valid START of a rune
// the prefix does not finish — a genuinely invalid tail is left alone,
// so the verdict on real garbage is unchanged.
func TrimPartialRune(prefix []byte) []byte {
	for i := len(prefix) - 1; i >= 0 && i >= len(prefix)-3; i-- {
		b := prefix[i]
		if b&0xc0 == 0x80 {
			continue // a continuation byte: keep walking back to its lead
		}
		if b&0x80 == 0 {
			return prefix // ASCII: the tail is complete
		}
		// A lead byte, and only a REAL one: 0xf8 and up start no rune in
		// UTF-8, so bytes like those are invalid wherever the bound
		// fell and trimming them would launder garbage into text.
		var want int
		switch {
		case b&0xe0 == 0xc0:
			want = 2
		case b&0xf0 == 0xe0:
			want = 3
		case b&0xf8 == 0xf0:
			want = 4
		default:
			return prefix
		}
		if len(prefix)-i < want {
			return prefix[:i]
		}
		return prefix
	}
	return prefix
}