77 lines
2.4 KiB
Go
77 lines
2.4 KiB
Go
package store
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Meta is the flat per-object record stored alongside the blob. Its presence on
|
|
// disk is what makes an object visible; an object directory without one is
|
|
// either mid-upload or crash debris.
|
|
type Meta struct {
|
|
ID string `json:"id"`
|
|
Filename string `json:"filename"`
|
|
Size int64 `json:"size"`
|
|
SHA256 string `json:"sha256"`
|
|
Created time.Time `json:"created"`
|
|
Expires *time.Time `json:"expires"` // nil means never
|
|
Owner string `json:"owner"` // "" means anonymous
|
|
Vanity bool `json:"vanity"`
|
|
|
|
// DeleteHash is the SHA-256 of the delete token handed to the uploader.
|
|
// The token itself is shown once and never stored.
|
|
DeleteHash string `json:"delete_hash"`
|
|
}
|
|
|
|
// Expired reports whether the object's lifetime has run out.
|
|
func (m *Meta) Expired(now time.Time) bool {
|
|
return m.Expires != nil && !now.Before(*m.Expires)
|
|
}
|
|
|
|
const fallbackFilename = "download.bin"
|
|
|
|
// maxFilenameBytes matches the common filesystem limit; the name is only ever
|
|
// metadata here, but keeping it bounded keeps headers and pages sane.
|
|
const maxFilenameBytes = 255
|
|
|
|
// SanitizeFilename reduces a caller-supplied filename to something safe to put
|
|
// in a Content-Disposition header and to show on a page.
|
|
//
|
|
// The result is never used to build a path - paths come from CleanID alone -
|
|
// so this guards against header injection and display confusion rather than
|
|
// traversal. Separators are stripped regardless, so that a name surviving to
|
|
// some future code path cannot carry a directory with it.
|
|
func SanitizeFilename(name string) string {
|
|
// Take the last element under either separator convention: browsers on
|
|
// Windows have historically sent full paths.
|
|
if i := strings.LastIndexAny(name, `/\`); i >= 0 {
|
|
name = name[i+1:]
|
|
}
|
|
if !utf8.ValidString(name) {
|
|
name = strings.ToValidUTF8(name, "")
|
|
}
|
|
name = strings.Map(func(r rune) rune {
|
|
switch {
|
|
case r < 0x20, r == 0x7f: // control characters, CR and LF included
|
|
return -1
|
|
case r == '/', r == '\\', r == 0:
|
|
return -1
|
|
}
|
|
return r
|
|
}, name)
|
|
name = strings.TrimSpace(name)
|
|
|
|
if len(name) > maxFilenameBytes {
|
|
name = name[:maxFilenameBytes]
|
|
// Do not leave a partial rune at the end.
|
|
for len(name) > 0 && !utf8.ValidString(name) {
|
|
name = name[:len(name)-1]
|
|
}
|
|
}
|
|
if name == "" || name == "." || name == ".." {
|
|
return fallbackFilename
|
|
}
|
|
return name
|
|
}
|