mirror of
https://gitee.com/rainbond/Rainbond.git
synced 2024-12-05 05:07:38 +08:00
209 lines
4.8 KiB
Go
209 lines
4.8 KiB
Go
package upload
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"errors"
|
|
"io"
|
|
"io/ioutil"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Error incomplete returned by uploader when loaded non-last chunk.
|
|
var incomplete = errors.New("incomplete")
|
|
|
|
// Structure describes the state of the original file.
|
|
type originalFile struct {
|
|
BaseMime string
|
|
Filepath string
|
|
Filename string
|
|
Size int64
|
|
}
|
|
|
|
func (ofile *originalFile) Ext() string {
|
|
return strings.ToLower(filepath.Ext(ofile.Filename))
|
|
}
|
|
|
|
func process(req *http.Request, storage string) ([]*originalFile, error) {
|
|
meta, err := parseMeta(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
body, err := newBody(req.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
up := &uploader{Root: storage, Meta: meta, body: body}
|
|
|
|
files, err := up.SaveFiles()
|
|
if err == incomplete {
|
|
return files, err
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return files, nil
|
|
}
|
|
|
|
// Upload manager.
|
|
type uploader struct {
|
|
Root string
|
|
Meta *meta
|
|
body *body
|
|
}
|
|
|
|
// Function SaveFiles sequentially loads the original files or chunk's.
|
|
func (up *uploader) SaveFiles() ([]*originalFile, error) {
|
|
files := make([]*originalFile, 0)
|
|
for {
|
|
ofile, err := up.SaveFile()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
files = append(files, ofile)
|
|
}
|
|
|
|
return files, nil
|
|
}
|
|
|
|
// Function loads one or download the original file chunk.
|
|
// Asks for the starting position in the body of the request to read the next file.
|
|
// Asks for a temporary file.
|
|
// Writes data from the request body into a temporary file.
|
|
// Specifies the size of the resulting temporary file.
|
|
// If the query specified header Content-Range,
|
|
// and the size of the resulting file does not match, it returns an error incomplete.
|
|
// Otherwise, defines the basic mime type, and returns the original file.
|
|
func (up *uploader) SaveFile() (*originalFile, error) {
|
|
body, filename, err := up.Reader()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
temp_file, err := up.tempFile()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer temp_file.Close()
|
|
|
|
if err = up.Write(temp_file, body); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fi, err := temp_file.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ofile := &originalFile{Filename: filename, Filepath: temp_file.Name(), Size: fi.Size()}
|
|
|
|
ofile.BaseMime, err = identifyMime(ofile.Filepath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ofile, nil
|
|
}
|
|
|
|
// Returns the reader to read the file or chunk of request body and the original file name.
|
|
// If the request header Content-Type is multipart/form-data, returns the next copy part.
|
|
// If all of part read the case of binary loading read the request body, an error is returned io.EOF.
|
|
func (up *uploader) Reader() (io.Reader, string, error) {
|
|
if up.Meta.MediaType == "multipart/form-data" {
|
|
if up.body.MR == nil {
|
|
up.body.MR = multipart.NewReader(up.body.body, up.Meta.Boundary)
|
|
}
|
|
for {
|
|
part, err := up.body.MR.NextPart()
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
if part.FormName() == "files[]" {
|
|
return part, part.FileName(), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
if !up.body.Available {
|
|
return nil, "", io.EOF
|
|
}
|
|
|
|
up.body.Available = false
|
|
|
|
return up.body.body, up.Meta.Filename, nil
|
|
}
|
|
|
|
// Returns a temporary file to download the file or resume chunk.
|
|
func (up *uploader) tempFile() (*os.File, error) {
|
|
return tempFile()
|
|
}
|
|
|
|
// Returns the newly created temporary file.
|
|
func tempFile() (*os.File, error) {
|
|
return ioutil.TempFile(os.TempDir(), "coquelicot")
|
|
}
|
|
|
|
// Returns a temporary file to download chunk.
|
|
// To calculate a unique file name used cookie named coquelicot and the original file name.
|
|
// File located in the directory chunks storage root directory.
|
|
// Before returning the file pointer is shifted by the value of offset,
|
|
// in a situation where the pieces are loaded from the second to the last.
|
|
func tempFileChunks(offset int64, storage, upload_sid, user_filename string) (*os.File, error) {
|
|
hasher := md5.New()
|
|
hasher.Write([]byte(upload_sid + user_filename))
|
|
filename := hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
path := filepath.Join(storage, "chunks")
|
|
|
|
err := os.MkdirAll(path, 0755)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
file, err := os.OpenFile(filepath.Join(path, filename), os.O_CREATE|os.O_WRONLY, 0664)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err = file.Seek(offset, 0); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return file, nil
|
|
}
|
|
|
|
// The function writes a temporary file value from reader.
|
|
func (up *uploader) Write(temp_file *os.File, body io.Reader) error {
|
|
var err error
|
|
_, err = io.Copy(temp_file, body)
|
|
return err
|
|
}
|
|
|
|
// identifyMine gets base mime type.
|
|
func identifyMime(file string) (string, error) {
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer f.Close()
|
|
// DetectContentType reads at most the first 512 bytes
|
|
buf := make([]byte, 512)
|
|
_, err = f.Read(buf)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
mime := strings.Split(http.DetectContentType(buf), "/")[0]
|
|
|
|
return mime, nil
|
|
}
|