package contented import ( "crypto/sha512" "encoding/hex" "encoding/json" "io" "log" "mime" "mime/multipart" "net/http" "os" "path" "path/filepath" "strings" "time" ) func (this *Server) handleUpload(w http.ResponseWriter, r *http.Request) { if !this.opts.EnableUpload { http.Error(w, "Server is read-only", 403) return } remoteIP := this.remoteIP(r) err := r.ParseMultipartForm(0) // buffer upload in temporary files on disk, not memory if err != nil { log.Printf("%s Invalid request: %s\n", remoteIP, err.Error()) http.Error(w, "Invalid request", 400) return } if r.MultipartForm == nil || r.MultipartForm.File == nil || len(r.MultipartForm.File["f"]) < 1 { log.Printf("%s Invalid request: no multipart content\n", remoteIP) http.Error(w, "Invalid request", 400) return } ret := make([]string, 0, len(r.MultipartForm.File["f"])) for _, fhs := range r.MultipartForm.File["f"] { f, err := fhs.Open() if err != nil { log.Printf("%s Internal error: %s\n", remoteIP, err.Error()) http.Error(w, "Internal error", 500) return } path, err := this.handleUploadFile(f, fhs, remoteIP) if err != nil { log.Printf("%s Upload failed: %s\n", remoteIP, err.Error()) http.Error(w, "Upload failed", 500) } ret = append(ret, path) } jb, err := json.Marshal(ret) if err != nil { log.Printf("%s Internal error: %s\n", remoteIP, err.Error()) http.Error(w, "Internal error", 500) return } w.Header().Set(`Content-Type`, `application/json`) w.WriteHeader(200) w.Write(jb) } func (this *Server) handleUploadFile(src multipart.File, hdr *multipart.FileHeader, UploadIP string) (string, error) { // Get file length srcLen, err := src.Seek(0, io.SeekEnd) if err != nil { return "", err } _, err = src.Seek(0, io.SeekStart) if err != nil { return "", err } // Get file hash hasher := sha512.New512_256() _, err = io.CopyN(hasher, src, int64(srcLen)) if err != nil { return "", err } _, err = src.Seek(0, io.SeekStart) if err != nil { return "", err } // Save file to disk fileHash := hex.EncodeToString(hasher.Sum(nil)) dest, err := os.OpenFile(filepath.Join(this.opts.DataDirectory, fileHash), os.O_CREATE|os.O_WRONLY, this.opts.FileMode()) shouldSave := true if err != nil && os.IsExist(err) { // hash matches existing upload // That's fine - but still persist the metadata separately shouldSave = false } else if err != nil { return "", err } if shouldSave { defer dest.Close() _, err = io.CopyN(dest, src, int64(srcLen)) if err != nil { return "", err } } // Determine mime type ctype := hdr.Header.Get("Content-Type") if ctype == "" { ctype = mime.TypeByExtension(path.Ext(hdr.Filename)) } if ctype == "" { ctype = `application/octet-stream` } // Blacklisted mime-types ctype = strings.ToLower(ctype) if strings.HasPrefix(ctype, `text/html`) || strings.HasPrefix(ctype, `application/javascript`) { ctype = `application/octet-stream` } // Persist metadata to DB m := Metadata{ FileHash: fileHash, Filename: hdr.Filename, UploadTime: time.Now(), UploadIP: UploadIP, FileSize: srcLen, MimeType: ctype, } fileRef, err := this.AddMetadata(m) if err != nil { return "", err } // Done return fileRef, nil }