big update: added reactions, groups, media share
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
package media
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Local stores objects as files under a base directory.
|
||||
type Local struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
// NewLocal creates a Local store rooted at dir, creating it if needed.
|
||||
func NewLocal(dir string) (*Local, error) {
|
||||
if err := os.MkdirAll(dir, 0o750); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Local{dir: dir}, nil
|
||||
}
|
||||
|
||||
func (l *Local) resolve(key string) (string, error) {
|
||||
clean := filepath.Clean(filepath.Join(l.dir, key))
|
||||
if !hasPrefix(clean, l.dir) {
|
||||
return "", errors.New("media: invalid storage key")
|
||||
}
|
||||
return clean, nil
|
||||
}
|
||||
|
||||
// Save writes the stream to disk atomically under the base directory.
|
||||
func (l *Local) Save(_ context.Context, key string, r io.Reader) error {
|
||||
path, err := l.resolve(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
|
||||
return err
|
||||
}
|
||||
tmp, err := os.CreateTemp(filepath.Dir(path), ".upload-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmp.Name())
|
||||
|
||||
if _, err := io.Copy(tmp, r); err != nil {
|
||||
tmp.Close()
|
||||
return err
|
||||
}
|
||||
if err := tmp.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmp.Name(), path)
|
||||
}
|
||||
|
||||
// Open returns a reader for the object or ErrNotFound.
|
||||
func (l *Local) Open(_ context.Context, key string) (io.ReadCloser, error) {
|
||||
path, err := l.resolve(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Delete removes the object; missing objects are not an error.
|
||||
func (l *Local) Delete(_ context.Context, key string) error {
|
||||
path, err := l.resolve(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasPrefix(p, dir string) bool {
|
||||
rel, err := filepath.Rel(dir, p)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return rel != ".." && !startsWithDotDot(rel)
|
||||
}
|
||||
|
||||
func startsWithDotDot(rel string) bool {
|
||||
return len(rel) >= 3 && rel[0] == '.' && rel[1] == '.' && (rel[2] == '/' || rel[2] == '\\')
|
||||
}
|
||||
Reference in New Issue
Block a user