aboutsummaryrefslogtreecommitdiffstats
path: root/storage.go
diff options
context:
space:
mode:
authorclsr <clsr@clsr.net>2016-11-15 16:57:51 +0100
committerclsr <clsr@clsr.net>2016-11-15 19:28:55 +0100
commitd3536e6741351fb13a9f6a327637bc2a4619fea4 (patch)
treef9cf707ed2b3daf6bea2d7a07f0c1ce9efbe5c23 /storage.go
parent1e55e61786eaedd59c067f4306fb8427dc92ef52 (diff)
downloadgomf-d3536e6741351fb13a9f6a327637bc2a4619fea4.tar.gz
gomf-d3536e6741351fb13a9f6a327637bc2a4619fea4.zip
Move storage to its own packagev0.5.0
Diffstat (limited to 'storage.go')
-rw-r--r--storage.go280
1 files changed, 0 insertions, 280 deletions
diff --git a/storage.go b/storage.go
deleted file mode 100644
index c0e8130..0000000
--- a/storage.go
+++ /dev/null
@@ -1,280 +0,0 @@
-package main
-
-import (
- "crypto/sha1"
- "encoding/base64"
- "encoding/hex"
- "errors"
- "io"
- "io/ioutil"
- "math/rand"
- "mime"
- "os"
- "path"
- "path/filepath"
- "strconv"
- "strings"
- "time"
-)
-
-const (
- MaxIdTries = 64
-
- DefaultIdCharset = "abcdefghijklmnopqrstuvwxyz"
- DefaultIdLength = 6
- DefaultMaxSize = 50 * 1024 * 1024
-)
-
-type Storage struct {
- Folder string
- IdCharset string
- IdLength int
- MaxSize int64
- FilterMime []string
- FilterExt []string
- Whitelist bool
-}
-
-type ErrForbidden struct{ Type string }
-
-func (e ErrForbidden) Error() string { return "forbidden type: " + e.Type }
-
-type ErrTooLarge struct{ Size int64 }
-
-func (e ErrTooLarge) Error() string {
- return "file exceeds maximum allowed size of " + strconv.FormatInt(e.Size, 10) + " bytes"
-}
-
-type ErrNotFound struct{ Name string }
-
-func (e ErrNotFound) Error() string { return "file " + e.Name + " not found" }
-
-func NewStorage(folder string) *Storage {
- if err := os.MkdirAll(path.Join(folder, "temp"), 0755); err != nil {
- panic(err)
- }
- if err := os.MkdirAll(path.Join(folder, "files"), 0755); err != nil {
- panic(err)
- }
- if err := os.MkdirAll(path.Join(folder, "ids"), 0755); err != nil {
- panic(err)
- }
-
- return &Storage{
- Folder: folder,
- IdCharset: DefaultIdCharset,
- IdLength: DefaultIdLength,
- MaxSize: DefaultMaxSize,
- }
-}
-
-func (s *Storage) Get(id string) (file *os.File, hash string, size int64, modtime time.Time, err error) {
- ext := path.Ext(id)
- id = id[:len(id)-len(ext)]
- for i := 0; i < len(id); i++ {
- if !strings.ContainsRune(s.IdCharset, rune(id[i])) {
- err = errors.New("invalid ID: " + id)
- return
- }
- }
- folder := s.idToFolder("ids", id)
- files, err := ioutil.ReadDir(folder)
- if err != nil {
- err = ErrNotFound{id + ext}
- return
- }
- if len(files) < 1 {
- err = errors.New("internal storage error")
- return
- }
- fn := files[0].Name()
- fp := path.Join(folder, fn)
- target, err := os.Readlink(fp)
- if err != nil {
- return
- }
- bhash, err := base64.RawURLEncoding.DecodeString(path.Base(path.Dir(target)))
- if err != nil {
- return
- }
- hash = hex.EncodeToString(bhash)
- if path.Ext(fn) != ext {
- err = ErrNotFound{id + ext}
- return
- }
- stat, err := os.Lstat(fp)
- if err != nil {
- return
- }
- modtime = stat.ModTime()
- size = stat.Size()
- file, err = os.Open(fp)
- return
-}
-
-var errFileExists = errors.New("file exists")
-
-func (s *Storage) New(r io.Reader, name string) (id, hash string, size int64, err error) {
- temp, err := ioutil.TempFile(path.Join(s.Folder, "temp"), "file")
- if err != nil {
- return
- }
- defer func() {
- if temp != nil {
- temp.Close()
- os.Remove(temp.Name())
- }
- }()
-
- hash, size, err = s.readInput(temp, r)
- if err != nil {
- return
- }
- _, ext, err := s.getMimeExt(temp.Name(), name)
- if err != nil {
- return
- }
- id, err = s.storeFile(temp, hash, ext)
- if err == nil {
- temp = nil // prevent deletion
- } else if err == errFileExists {
- err = nil
- }
-
- return
-}
-
-func (s *Storage) randomId() string {
- id := make([]byte, s.IdLength)
- for i := 0; i < len(id); i++ {
- id[i] = s.IdCharset[rand.Intn(len(s.IdCharset))]
- }
- return string(id)
-}
-
-func (s *Storage) idToFolder(subfolder, id string) string {
- name := id
- for len(name) < 4 {
- name = "_" + name
- }
- return path.Join(s.Folder, subfolder, name[0:2], name[2:4], id)
-}
-
-func (s *Storage) readInput(w io.Writer, r io.Reader) (hash string, size int64, err error) {
- h := sha1.New()
- w = io.MultiWriter(h, w)
- if s.MaxSize > 0 {
- r = io.LimitReader(r, s.MaxSize+1)
- }
- size, err = io.Copy(w, r)
- if err != nil {
- return
- }
- if lr, ok := r.(*io.LimitedReader); ok && lr.N == 0 {
- err = ErrTooLarge{s.MaxSize}
- return
- }
- hash = base64.RawURLEncoding.EncodeToString(h.Sum(nil))
- return
-}
-
-func (s *Storage) getMimeExt(fpath string, name string) (mimetype, ext string, err error) {
- mimetype, err = GetMimeType(fpath)
- if err != nil {
- return
- }
-
- // choose file extension, prefer the user-provided one
- ext = path.Ext(name)
- exts, err := mime.ExtensionsByType(mimetype)
- if err != nil {
- return
- }
- if ext != "" && find(exts, ext) == "" {
- ext = ""
- if len(exts) > 0 {
- ext = exts[0]
- }
- }
-
- filtered, ok := s.findFilter(exts, mimetype)
- if !ok && s.Whitelist { // whitelist: reject if not on filters
- err = ErrForbidden{mimetype}
- } else if ok && !s.Whitelist { // blacklist: reject if filtered
- // only block application/octet-stream if explicitly requested
- if mimetype != "application/octet-stream" || find(s.FilterMime, mimetype) != "" {
- err = ErrForbidden{filtered}
- }
- }
-
- return
-}
-
-func (s *Storage) findFilter(exts []string, mimetype string) (match string, ok bool) {
- if m := find(s.FilterMime, mimetype); m == mimetype {
- return m, true
- }
- for _, ext := range exts {
- if e := find(s.FilterExt, ext); e == ext {
- return e, true
- }
- }
- return "", false
-}
-
-func (s *Storage) storeFile(file *os.File, hash, ext string) (id string, err error) {
- hfolder := s.idToFolder("files", hash)
- hpath := path.Join(hfolder, "file")
- fexists := false
-
- os.MkdirAll(path.Dir(hfolder), 0755)
- err = os.Mkdir(hfolder, 0755)
- if err != nil {
- if _, err = os.Stat(hpath); err != nil {
- err = errors.New("internal storage error")
- }
- fexists = true
- } else {
- err = os.Rename(file.Name(), hpath)
- os.Chmod(hpath, 0644)
- }
- if err != nil {
- return
- }
-
- fpath := ""
- for i := 0; i < MaxIdTries; i++ {
- id = s.randomId()
- dir := s.idToFolder("ids", id)
- os.MkdirAll(path.Dir(dir), 0755)
- err = os.Mkdir(dir, 0755)
- if err == nil {
- fpath = path.Join(dir, "file"+ext)
- id += ext
- break
- }
- }
- if fpath == "" {
- err = errors.New("internal storage error")
- return
- }
- rhpath, err := filepath.Rel(path.Dir(fpath), hpath)
- if err != nil {
- return
- }
- err = os.Symlink(rhpath, fpath)
-
- if fexists && err == nil {
- err = errFileExists
- }
- return
-}
-
-func find(ss []string, search string) string {
- for _, s := range ss {
- if s == search {
- return s
- }
- }
- return ""
-}