file/file.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) {
}