297 lines
7.3 KiB
Go
297 lines
7.3 KiB
Go
package file
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gomango/auth"
|
|
"gopkg.in/mgo.v2"
|
|
"gopkg.in/mgo.v2/bson"
|
|
)
|
|
|
|
var bsonRegex = regexp.MustCompile(`[a-fA-F0-9]{24}`)
|
|
|
|
type Metadata struct {
|
|
AccountId string `bson:"account_id,omitempty"`
|
|
ProfileId string `bson:"profile_id,omitempty"`
|
|
}
|
|
|
|
type FileHandler struct {
|
|
options FileHandlerOptions
|
|
}
|
|
|
|
type FileHandlerOptions struct {
|
|
Prefix string // Prefix is more or less the collection name
|
|
DB string // Database name
|
|
MS *mgo.Session // The main mgo Session
|
|
AllowDuplicate bool // Wether to allow more than 1 same named file
|
|
DisplayIndex bool // wether to display a directory index consisting of all files
|
|
}
|
|
|
|
func NewFileHandler(opts FileHandlerOptions) *FileHandler {
|
|
return &FileHandler{options: opts}
|
|
}
|
|
|
|
func (h *FileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case "GET":
|
|
switch r.URL.Path {
|
|
case "":
|
|
if !h.options.DisplayIndex {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
ms := h.options.MS.Copy()
|
|
defer ms.Close()
|
|
gfs := ms.DB(h.options.DB).GridFS(h.options.Prefix)
|
|
type File struct {
|
|
Id string `json:"id"`
|
|
Name string `json:"name"`
|
|
Size int64 `json:"size,string"`
|
|
ContentType string `json:"contenttype"`
|
|
UploadDate time.Time `json:"uploaddate"`
|
|
}
|
|
it := gfs.Find(nil).Iter()
|
|
var f *mgo.GridFile
|
|
files := []*File{}
|
|
for gfs.OpenNext(it, &f) {
|
|
id, _ := f.Id().(bson.ObjectId)
|
|
files = append(files, &File{Id: id.Hex(), Name: f.Name(), Size: f.Size(), ContentType: f.ContentType(), UploadDate: f.UploadDate()})
|
|
}
|
|
if it.Close() != nil {
|
|
panic(it.Close())
|
|
}
|
|
if e := writeJSON(w, files); e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
}
|
|
default:
|
|
ms := h.options.MS.Copy()
|
|
defer ms.Close()
|
|
gfs := ms.DB(h.options.DB).GridFS(h.options.Prefix)
|
|
var file *mgo.GridFile
|
|
var e error
|
|
if h.options.AllowDuplicate {
|
|
if !bsonRegex.MatchString(r.URL.Path) {
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
return
|
|
}
|
|
// find by id
|
|
file, e = gfs.OpenId(bson.ObjectIdHex(r.URL.Path))
|
|
if e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
} else {
|
|
// find by filename
|
|
file, e = gfs.Open(r.URL.Path)
|
|
if e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
w.Header().Set("Content-Type", file.ContentType())
|
|
w.Header().Set("Content-Length", strconv.FormatInt(file.Size(), 10))
|
|
if n, e := io.Copy(w, file); e != nil {
|
|
panic(e)
|
|
} else if n != file.Size() {
|
|
panic("file: size and number written not the same")
|
|
}
|
|
if e := file.Close(); e != nil {
|
|
panic(e)
|
|
}
|
|
|
|
}
|
|
case "PUT":
|
|
// check token
|
|
|
|
ts := tokenFromRequest(r)
|
|
if ts == "" {
|
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
token, e := auth.VerifyToken(ts)
|
|
if e != nil {
|
|
http.Error(w, e.Error(), http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
// upload and insert
|
|
ms := h.options.MS.Copy()
|
|
defer ms.Close()
|
|
gfs := ms.DB(h.options.DB).GridFS(h.options.Prefix)
|
|
if !h.options.AllowDuplicate {
|
|
// No duplicate files
|
|
//c := ms.DB(h.options.DB).C(h.options.Prefix + ".files")
|
|
c := gfs.Files
|
|
query := c.Find(bson.M{"filename": r.URL.Path})
|
|
count, e := query.Count()
|
|
|
|
if e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if count > 0 {
|
|
type File struct {
|
|
Id bson.ObjectId `bson:_id"`
|
|
ChunkSize int64 `bson:"chunkSize"`
|
|
UploadDate time.Time `bson:"uploadDate"`
|
|
Length int64 `bson:"length"`
|
|
MD5 string `bson:"md5"`
|
|
Filename string `bson:"filename"`
|
|
}
|
|
f := new(File)
|
|
if e := query.One(f); e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if e := gfs.Remove(f.Filename); e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
f, e := gfs.Create(r.URL.Path)
|
|
if e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
b := bytes.NewBuffer(nil)
|
|
n, e := io.Copy(b, r.Body)
|
|
if e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
meta := new(Metadata)
|
|
meta.AccountId = token.Claims.AccountId
|
|
meta.ProfileId = token.Claims.ProfileId
|
|
|
|
f.SetMeta(meta)
|
|
f.SetContentType(http.DetectContentType(b.Bytes()))
|
|
|
|
n, e = io.Copy(f, b)
|
|
if e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if e := f.Close(); e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
id, _ := f.Id().(bson.ObjectId)
|
|
|
|
out := struct {
|
|
Id string `json:"id"`
|
|
Written int64 `json:"written,string"`
|
|
}{
|
|
Id: id.Hex(),
|
|
Written: n,
|
|
}
|
|
if e := writeJSON(w, out); e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
}
|
|
case "DELETE":
|
|
// check token
|
|
|
|
ts := tokenFromRequest(r)
|
|
if ts == "" {
|
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
token, e := auth.VerifyToken(ts)
|
|
if e != nil {
|
|
http.Error(w, e.Error(), http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
ms := h.options.MS.Copy()
|
|
defer ms.Close()
|
|
gfs := ms.DB(h.options.DB).GridFS(h.options.Prefix)
|
|
|
|
isadmin := false
|
|
for _, role := range token.Claims.Roles {
|
|
if role == "admin" {
|
|
isadmin = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if h.options.AllowDuplicate {
|
|
if !bsonRegex.MatchString(r.URL.Path) {
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
return
|
|
}
|
|
file, e := gfs.OpenId(bson.ObjectIdHex(r.URL.Path))
|
|
if e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
meta := new(Metadata)
|
|
|
|
if e := file.GetMeta(meta); e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if e := file.Close(); e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if token.Claims.AccountId != meta.AccountId {
|
|
if !isadmin {
|
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
|
}
|
|
return
|
|
}
|
|
// remove file by id
|
|
if e := gfs.RemoveId(bson.ObjectIdHex(r.URL.Path)); e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.WriteHeader(200)
|
|
} else {
|
|
file, e := gfs.Open(r.URL.Path)
|
|
if e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
meta := new(Metadata)
|
|
|
|
if e := file.GetMeta(meta); e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if e := file.Close(); e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if token.Claims.AccountId != meta.AccountId {
|
|
if !isadmin {
|
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
|
}
|
|
return
|
|
}
|
|
|
|
// remove file by name
|
|
if e := gfs.Remove(r.URL.Path); e != nil {
|
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
|
}
|
|
w.WriteHeader(200)
|
|
}
|
|
|
|
default:
|
|
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func AddFile(w http.ResponseWriter, r *http.Request) {
|
|
|
|
}
|