mirror of
https://github.com/dutchcoders/transfer.sh.git
synced 2020-11-18 19:53:40 -08:00
Add object metadata
- support for max downloads - support for expiring downloads
This commit is contained in:
parent
45bafbe48f
commit
989debecb5
@ -32,6 +32,7 @@ import (
|
|||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
@ -48,6 +49,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
text_template "text/template"
|
text_template "text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -256,6 +258,19 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
contentLength := n
|
contentLength := n
|
||||||
|
|
||||||
|
metadata := MetadataForRequest(contentType, r)
|
||||||
|
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
|
||||||
|
log.Printf("%s", err.Error())
|
||||||
|
http.Error(w, errors.New("Could not encode metadata").Error(), 500)
|
||||||
|
return
|
||||||
|
} else if err := s.storage.Put(token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
|
||||||
|
log.Printf("%s", err.Error())
|
||||||
|
http.Error(w, errors.New("Could not save metadata").Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType)
|
log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType)
|
||||||
|
|
||||||
if err = s.storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil {
|
if err = s.storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil {
|
||||||
@ -271,6 +286,42 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
// ContentType is the original uploading content type
|
||||||
|
ContentType string
|
||||||
|
// Secret as knowledge to delete file
|
||||||
|
// Secret string
|
||||||
|
// Downloads is the actual number of downloads
|
||||||
|
Downloads int
|
||||||
|
// MaxDownloads contains the maximum numbers of downloads
|
||||||
|
MaxDownloads int
|
||||||
|
// MaxDate contains the max age of the file
|
||||||
|
MaxDate time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func MetadataForRequest(contentType string, r *http.Request) Metadata {
|
||||||
|
metadata := Metadata{
|
||||||
|
ContentType: contentType,
|
||||||
|
MaxDate: time.Now().Add(time.Hour * 24 * 365 * 10),
|
||||||
|
Downloads: 0,
|
||||||
|
MaxDownloads: 99999999,
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := r.Header.Get("Max-Downloads"); v == "" {
|
||||||
|
} else if v, err := strconv.Atoi(v); err != nil {
|
||||||
|
} else {
|
||||||
|
metadata.MaxDownloads = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := r.Header.Get("Max-Days"); v == "" {
|
||||||
|
} else if v, err := strconv.Atoi(v); err != nil {
|
||||||
|
} else {
|
||||||
|
metadata.MaxDate = time.Now().Add(time.Hour * 24 * time.Duration(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
|
|
||||||
@ -332,6 +383,19 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
token := Encode(10000000 + int64(rand.Intn(1000000000)))
|
token := Encode(10000000 + int64(rand.Intn(1000000000)))
|
||||||
|
|
||||||
|
metadata := MetadataForRequest(contentType, r)
|
||||||
|
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
|
||||||
|
log.Printf("%s", err.Error())
|
||||||
|
http.Error(w, errors.New("Could not encode metadata").Error(), 500)
|
||||||
|
return
|
||||||
|
} else if err := s.storage.Put(token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
|
||||||
|
log.Printf("%s", err.Error())
|
||||||
|
http.Error(w, errors.New("Could not save metadata").Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType)
|
log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -377,6 +441,63 @@ func getURL(r *http.Request) *url.URL {
|
|||||||
return &u
|
return &u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) Lock(token, filename string) error {
|
||||||
|
key := path.Join(token, filename)
|
||||||
|
|
||||||
|
if _, ok := s.locks[key]; !ok {
|
||||||
|
s.locks[key] = &sync.Mutex{}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.locks[key].Lock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Unlock(token, filename string) error {
|
||||||
|
key := path.Join(token, filename)
|
||||||
|
s.locks[key].Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) CheckMetadata(token, filename string) error {
|
||||||
|
s.Lock(token, filename)
|
||||||
|
defer s.Unlock(token, filename)
|
||||||
|
|
||||||
|
var metadata Metadata
|
||||||
|
|
||||||
|
r, _, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename))
|
||||||
|
if s.storage.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r).Decode(&metadata); err != nil {
|
||||||
|
return err
|
||||||
|
} else if metadata.Downloads >= metadata.MaxDownloads {
|
||||||
|
return errors.New("MaxDownloads expired.")
|
||||||
|
} else if time.Now().After(metadata.MaxDate) {
|
||||||
|
return errors.New("MaxDate expired.")
|
||||||
|
} else {
|
||||||
|
// todo(nl5887): mutex?
|
||||||
|
|
||||||
|
// update number of downloads
|
||||||
|
metadata.Downloads++
|
||||||
|
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
|
||||||
|
return errors.New("Could not encode metadata")
|
||||||
|
} else if err := s.storage.Put(token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
|
||||||
|
return errors.New("Could not save metadata")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
|
|
||||||
@ -400,6 +521,11 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
token := strings.Split(key, "/")[0]
|
token := strings.Split(key, "/")[0]
|
||||||
filename := sanitize(strings.Split(key, "/")[1])
|
filename := sanitize(strings.Split(key, "/")[1])
|
||||||
|
|
||||||
|
if err := s.CheckMetadata(token, filename); err != nil {
|
||||||
|
log.Printf("Error metadata: %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
reader, _, _, err := s.storage.Get(token, filename)
|
reader, _, _, err := s.storage.Get(token, filename)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -471,6 +597,11 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
token := strings.Split(key, "/")[0]
|
token := strings.Split(key, "/")[0]
|
||||||
filename := sanitize(strings.Split(key, "/")[1])
|
filename := sanitize(strings.Split(key, "/")[1])
|
||||||
|
|
||||||
|
if err := s.CheckMetadata(token, filename); err != nil {
|
||||||
|
log.Printf("Error metadata: %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
reader, _, contentLength, err := s.storage.Get(token, filename)
|
reader, _, contentLength, err := s.storage.Get(token, filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if s.storage.IsNotExist(err) {
|
if s.storage.IsNotExist(err) {
|
||||||
@ -523,6 +654,11 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
token := strings.Split(key, "/")[0]
|
token := strings.Split(key, "/")[0]
|
||||||
filename := strings.Split(key, "/")[1]
|
filename := strings.Split(key, "/")[1]
|
||||||
|
|
||||||
|
if err := s.CheckMetadata(token, filename); err != nil {
|
||||||
|
log.Printf("Error metadata: %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
reader, _, contentLength, err := s.storage.Get(token, filename)
|
reader, _, contentLength, err := s.storage.Get(token, filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if s.storage.IsNotExist(err) {
|
if s.storage.IsNotExist(err) {
|
||||||
@ -563,17 +699,21 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
token := vars["token"]
|
token := vars["token"]
|
||||||
filename := vars["filename"]
|
filename := vars["filename"]
|
||||||
|
|
||||||
reader, contentType, contentLength, err := s.storage.Get(token, filename)
|
if err := s.CheckMetadata(token, filename); err != nil {
|
||||||
if err != nil {
|
log.Printf("Error metadata: %s", err.Error())
|
||||||
if s.storage.IsNotExist(err) {
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
http.Error(w, "File not found", 404)
|
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
reader, contentType, contentLength, err := s.storage.Get(token, filename)
|
||||||
|
if s.storage.IsNotExist(err) {
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
log.Printf("%s", err.Error())
|
log.Printf("%s", err.Error())
|
||||||
http.Error(w, "Could not retrieve file.", 500)
|
http.Error(w, "Could not retrieve file.", 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -177,6 +178,8 @@ type Server struct {
|
|||||||
|
|
||||||
profilerEnabled bool
|
profilerEnabled bool
|
||||||
|
|
||||||
|
locks map[string]*sync.Mutex
|
||||||
|
|
||||||
storage Storage
|
storage Storage
|
||||||
|
|
||||||
forceHTTPs bool
|
forceHTTPs bool
|
||||||
@ -198,7 +201,9 @@ type Server struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(options ...OptionFn) (*Server, error) {
|
func New(options ...OptionFn) (*Server, error) {
|
||||||
s := &Server{}
|
s := &Server{
|
||||||
|
locks: map[string]*sync.Mutex{},
|
||||||
|
}
|
||||||
|
|
||||||
for _, optionFn := range options {
|
for _, optionFn := range options {
|
||||||
optionFn(s)
|
optionFn(s)
|
||||||
|
@ -72,6 +72,10 @@ func (s *LocalStorage) Get(token string, filename string) (reader io.ReadCloser,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalStorage) IsNotExist(err error) bool {
|
func (s *LocalStorage) IsNotExist(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return os.IsNotExist(err)
|
return os.IsNotExist(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +141,10 @@ func (s *S3Storage) Head(token string, filename string) (contentType string, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *S3Storage) IsNotExist(err error) bool {
|
func (s *S3Storage) IsNotExist(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("IsNotExist: %s, %#v", err.Error(), err)
|
log.Printf("IsNotExist: %s, %#v", err.Error(), err)
|
||||||
|
|
||||||
b := (err.Error() == "The specified key does not exist.")
|
b := (err.Error() == "The specified key does not exist.")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user