Documentation
¶
Overview ¶
Package blobstore stores end-to-end-encrypted attachment ciphertext as files on disk (SRV-07). It is deliberately dumb: it knows nothing about who owns a blob or when it expires -- that is internal/store's job -- and nothing about what the bytes mean, since they are ciphertext the server cannot read.
Files rather than a SQLite column: the driver has no incremental blob I/O, so a multi-megabyte column would be read and written as one []byte on the same single-writer connection that also serves authentication. Everything here streams through a fixed-size buffer instead, so memory use is independent of blob size.
Index ¶
- Variables
- func NewBlobID() (string, error)
- type Store
- func (s *Store) EachStoredID(fn func(blobID string) error) error
- func (s *Store) Open(blobID string) (*os.File, error)
- func (s *Store) Put(blobID string, src io.Reader, expectedDigest string, maxBytes int64) (written int64, digest string, err error)
- func (s *Store) Remove(blobID string) error
- func (s *Store) SweepTmp(olderThan time.Duration, now time.Time) (int, error)
Constants ¶
This section is empty.
Variables ¶
var ErrNotFound = errors.New("blobstore: blob not found")
ErrNotFound reports that no file exists for a blob id.
Functions ¶
func NewBlobID ¶
NewBlobID returns an unguessable blob id: 32 random bytes, hex-encoded.
Deliberately random rather than the content digest. Content addressing would make identical files share an id, letting anyone who holds a file test whether someone else uploaded the same one -- and turning the id into a probe for a blob's existence. Random ids also mean unguessability is a second line of defence behind the ownership check.
Types ¶
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a blob directory.
func (*Store) EachStoredID ¶
EachStoredID calls fn for every blob id currently on disk. Used by the orphan sweep to find files whose metadata row is gone (the window between writing the file and inserting the row, or a delete that failed halfway).
func (*Store) Open ¶
Open returns the blob's file for reading, for the caller to serve (e.g. via http.ServeContent, which needs an io.ReadSeeker for range requests). The caller closes it.
func (*Store) Put ¶
func (s *Store) Put(blobID string, src io.Reader, expectedDigest string, maxBytes int64) (written int64, digest string, err error)
Put streams src into the store under blobID, returning how many bytes were written and their SHA-256.
The caller passes the digest it expects (from the signed request header); on mismatch Put deletes the partial file and returns an error, so a body that does not match what was signed never becomes a stored blob. maxBytes bounds the write as a backstop -- the HTTP layer caps the body too, but this keeps the store safe for any caller.
Writes to a temp file, fsyncs, then renames into place: a torn write can only ever leave a temp file for the sweeper, never a corrupt blob.