Detect file types instead of trusting clients
This commit is contained in:
parent
f9c74a83f0
commit
eec5e4def4
@ -55,11 +55,11 @@ func (rl *rushlink) uploadFileGetHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := rl.fs.FilePath(fu.ID, fu.FileName)
|
filePath := fu.Path(rl.fs)
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
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")
|
renderError(w, r, http.StatusNotFound, "file not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -192,8 +192,7 @@ func (rl *rushlink) newFileUploadPasteHandler(w http.ResponseWriter, r *http.Req
|
|||||||
var paste *db.Paste
|
var paste *db.Paste
|
||||||
if err := rl.db.Bolt.Update(func(tx *bolt.Tx) error {
|
if err := rl.db.Bolt.Update(func(tx *bolt.Tx) error {
|
||||||
var err error
|
var err error
|
||||||
// Create the fileUpload in the database
|
fu, err = db.NewFileUpload(rl.fs, file, header.Filename)
|
||||||
fu, err = db.NewFileUpload(rl.fs, file, header.Filename, header.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(errors.Wrap(err, "creating fileUpload"))
|
panic(errors.Wrap(err, "creating fileUpload"))
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -76,14 +78,42 @@ func OpenFileStore(path string) (*FileStore, error) {
|
|||||||
return &FileStore{path[:]}, nil
|
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.
|
// 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()
|
id, err := uuid.NewRandom()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "generating UUID")
|
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 {
|
if err := os.Mkdir(path.Dir(filePath), dirMode); err != nil {
|
||||||
return nil, errors.Wrap(err, "creating file dir")
|
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()
|
defer file.Close()
|
||||||
|
|
||||||
hash := crc32.New(checksumTable)
|
// Write the file to disk
|
||||||
tee := io.TeeReader(r, hash)
|
_, err = io.Copy(file, &tmpBuf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "writing to file")
|
||||||
|
}
|
||||||
_, err = io.Copy(file, tee)
|
_, err = io.Copy(file, tee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "writing to file")
|
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
|
return fu, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FileStore) Path() string {
|
// GetFileUpload tries to retrieve a FileUpload object from the bolt database.
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetFileUpload(tx *bolt.Tx, id uuid.UUID) (*FileUpload, error) {
|
func GetFileUpload(tx *bolt.Tx, id uuid.UUID) (*FileUpload, error) {
|
||||||
bucket := tx.Bucket([]byte(BucketFileUpload))
|
bucket := tx.Bucket([]byte(BucketFileUpload))
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
@ -155,7 +178,7 @@ func (fu *FileUpload) Save(tx *bolt.Tx) error {
|
|||||||
// Delete deletes a FileUpload from the database.
|
// Delete deletes a FileUpload from the database.
|
||||||
func (fu *FileUpload) Delete(tx *bolt.Tx, fs *FileStore) error {
|
func (fu *FileUpload) Delete(tx *bolt.Tx, fs *FileStore) error {
|
||||||
// Remove the file in the backend
|
// Remove the file in the backend
|
||||||
filePath := fs.FilePath(fu.ID, fu.FileName)
|
filePath := fu.Path(fs)
|
||||||
if err := os.Remove(filePath); err != nil {
|
if err := os.Remove(filePath); err != nil {
|
||||||
return err
|
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)
|
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.
|
// URL returns the URL for the FileUpload.
|
||||||
func (fu *FileUpload) URL() *url.URL {
|
func (fu *FileUpload) URL() *url.URL {
|
||||||
rawurl := "/uploads/" + hex.EncodeToString(fu.ID[:]) + "/" + fu.FileName
|
rawurl := "/uploads/" + hex.EncodeToString(fu.ID[:]) + "/" + fu.FileName
|
||||||
|
Loading…
Reference in New Issue
Block a user