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) { }