Initial commit
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user