Detect file types instead of trusting clients

This commit is contained in:
Daan Sprenkels 2019-12-10 11:16:18 +01:00
parent f9c74a83f0
commit eec5e4def4
2 changed files with 47 additions and 20 deletions

View File

@ -55,11 +55,11 @@ func (rl *rushlink) uploadFileGetHandler(w http.ResponseWriter, r *http.Request)
panic(err)
}
filePath := rl.fs.FilePath(fu.ID, fu.FileName)
filePath := fu.Path(rl.fs)
file, err := os.Open(filePath)
if err != nil {
if os.IsNotExist(err) {
log.Printf("error: %v should exist according to the database, but it doesn't", filePath)
log.Printf("error: '%v' should exist according to the database, but it doesn't", filePath)
renderError(w, r, http.StatusNotFound, "file not found")
return
}
@ -192,8 +192,7 @@ func (rl *rushlink) newFileUploadPasteHandler(w http.ResponseWriter, r *http.Req
var paste *db.Paste
if err := rl.db.Bolt.Update(func(tx *bolt.Tx) error {
var err error
// Create the fileUpload in the database
fu, err = db.NewFileUpload(rl.fs, file, header.Filename, header.Header.Get("Content-Type"))
fu, err = db.NewFileUpload(rl.fs, file, header.Filename)
if err != nil {
panic(errors.Wrap(err, "creating fileUpload"))
}

View File

@ -1,9 +1,11 @@
package db
import (
"bytes"
"encoding/hex"
"hash/crc32"
"io"
"net/http"
"net/url"
"os"
"path"
@ -76,14 +78,42 @@ func OpenFileStore(path string) (*FileStore, error) {
return &FileStore{path[:]}, nil
}
// Path returns the path of the FileStore root.
func (fs *FileStore) Path() string {
return fs.path
}
// filePath resolves the path of a file in the FileStore given some id and filename.
func (fs *FileStore) filePath(id uuid.UUID, fileName string) string {
if fs.path == "" {
panic("fileStoreDir called while the file store path has not been set")
}
return path.Join(fs.path, hex.EncodeToString(id[:]), fileName)
}
// NewFileUpload creates a new FileUpload object.
func NewFileUpload(fs *FileStore, r io.Reader, fileName string, contentType string) (*FileUpload, error) {
//
// Internally, this function detects the type of the file stored in `r` using
// `http.DetectContentType`.
func NewFileUpload(fs *FileStore, r io.Reader, fileName string) (*FileUpload, error) {
// Generate a file ID
id, err := uuid.NewRandom()
if err != nil {
return nil, errors.Wrap(err, "generating UUID")
}
filePath := fs.FilePath(id, fileName)
// Construct a checksum for this file
hash := crc32.New(checksumTable)
tee := io.TeeReader(r, hash)
// Detect the file type
var tmpBuf bytes.Buffer
tmpBuf.Grow(512)
io.CopyN(&tmpBuf, tee, 512)
contentType := http.DetectContentType(tmpBuf.Bytes())
// Open the file on disk for writing
filePath := fs.filePath(id, fileName)
if err := os.Mkdir(path.Dir(filePath), dirMode); err != nil {
return nil, errors.Wrap(err, "creating file dir")
}
@ -93,8 +123,11 @@ func NewFileUpload(fs *FileStore, r io.Reader, fileName string, contentType stri
}
defer file.Close()
hash := crc32.New(checksumTable)
tee := io.TeeReader(r, hash)
// Write the file to disk
_, err = io.Copy(file, &tmpBuf)
if err != nil {
return nil, errors.Wrap(err, "writing to file")
}
_, err = io.Copy(file, tee)
if err != nil {
return nil, errors.Wrap(err, "writing to file")
@ -110,17 +143,7 @@ func NewFileUpload(fs *FileStore, r io.Reader, fileName string, contentType stri
return fu, nil
}
func (fs *FileStore) Path() string {
return fs.path
}
func (fs *FileStore) FilePath(id uuid.UUID, fileName string) string {
if fs.path == "" {
panic("fileStoreDir called while the file store path has not been set")
}
return path.Join(fs.path, hex.EncodeToString(id[:]), fileName)
}
// GetFileUpload tries to retrieve a FileUpload object from the bolt database.
func GetFileUpload(tx *bolt.Tx, id uuid.UUID) (*FileUpload, error) {
bucket := tx.Bucket([]byte(BucketFileUpload))
if bucket == nil {
@ -155,7 +178,7 @@ func (fu *FileUpload) Save(tx *bolt.Tx) error {
// Delete deletes a FileUpload from the database.
func (fu *FileUpload) Delete(tx *bolt.Tx, fs *FileStore) error {
// Remove the file in the backend
filePath := fs.FilePath(fu.ID, fu.FileName)
filePath := fu.Path(fs)
if err := os.Remove(filePath); err != nil {
return err
}
@ -173,6 +196,11 @@ func (fu *FileUpload) Delete(tx *bolt.Tx, fs *FileStore) error {
return errors.Wrap(os.Remove(path.Dir(filePath)), wrap)
}
// Path returns the path to this FileUpload in the FileStore provided in fs.
func (fu *FileUpload) Path(fs *FileStore) string {
return fs.filePath(fu.ID, fu.FileName)
}
// URL returns the URL for the FileUpload.
func (fu *FileUpload) URL() *url.URL {
rawurl := "/uploads/" + hex.EncodeToString(fu.ID[:]) + "/" + fu.FileName