megauploader/http.go
boyska 0ec0a97855 secure HTTP headers
CSP, frame, XSS, etc.
2017-12-19 13:51:11 +01:00

175 lines
5 KiB
Go

package megauploader
//go:generate rice embed-go
// this file contains all the web-related code for MegaUploader
import (
"encoding/json"
"fmt"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"sync"
rice "github.com/GeertJohan/go.rice"
"github.com/c2h5oh/datasize"
"github.com/unrolled/secure"
)
// MegaUploader acts as controller for all the application. Since this is inherently a web-focused
// application, it will include http utils
type MegaUploader struct {
Conf Config
configLock *sync.RWMutex
secureMW *secure.Secure
}
// NewMegaUploader create a new mega uploader
func NewMegaUploader(c Config) MegaUploader {
return MegaUploader{
Conf: c,
configLock: new(sync.RWMutex),
secureMW: secure.New(secure.Options{
FrameDeny: true,
ContentTypeNosniff: true,
BrowserXssFilter: true,
ContentSecurityPolicy: "default-src 'self'",
}),
}
}
func (mu *MegaUploader) ChangeConf(newconf Config) {
mu.configLock.Lock()
mu.Conf = newconf
mu.configLock.Unlock()
}
// confAcquire is a middleware to read-lock configuration
func (mu *MegaUploader) confAcquire(inner http.Handler) http.Handler {
f := func(w http.ResponseWriter, r *http.Request) {
mu.configLock.RLock()
inner.ServeHTTP(w, r)
mu.configLock.RUnlock()
}
return http.HandlerFunc(f)
}
func (mu *MegaUploader) privateMiddleware(inner func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return mu.secureMW.Handler(mu.confAcquire(requireUserMiddleware(inner))).ServeHTTP
}
func (mu *MegaUploader) publicMiddleware(inner func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return mu.secureMW.Handler(requireUserMiddleware(inner)).ServeHTTP
}
// SetupRoutes adds API routes
func (mu *MegaUploader) SetupRoutes() {
prefix := strings.TrimRight(mu.Conf.Global.RoutePrefix, "/")
http.HandleFunc(prefix+"/", mu.privateMiddleware(mu.home))
http.HandleFunc(prefix+"/upload/", mu.privateMiddleware(mu.uploadUI))
static := rice.MustFindBox("res/static")
http.HandleFunc(prefix+"/static/", mu.publicMiddleware(
http.StripPrefix(prefix+"/static/", http.FileServer(static.HTTPBox())).ServeHTTP,
))
http.HandleFunc(prefix+"/api/share", mu.privateMiddleware(mu.listShares))
http.HandleFunc(prefix+"/api/share/", mu.privateMiddleware(mu.getShare))
http.HandleFunc(prefix+"/api/upload/", mu.privateMiddleware(mu.upload))
}
func getUser(r *http.Request) (string, error) {
user := r.Header.Get("X-Forwarded-User")
if user == "" {
return "", fmt.Errorf("User not set")
}
return user, nil
}
func requireUserMiddleware(inner func(w http.ResponseWriter, r *http.Request)) http.Handler {
f := func(w http.ResponseWriter, r *http.Request) {
_, err := getUser(r)
if err != nil {
http.Error(w, "Only logged-in users are authorized", http.StatusUnauthorized)
return
}
inner(w, r)
}
return http.HandlerFunc(f)
}
func (mu *MegaUploader) listShares(w http.ResponseWriter, r *http.Request) {
user, _ := getUser(r) // user is set: checked by middleware
shares := mu.Conf.GetAuthShares(user)
serialized, err := json.Marshal(shares)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Header().Set("Content-type", "application/json")
w.Write(serialized)
}
func (mu *MegaUploader) getShare(w http.ResponseWriter, r *http.Request) {
sharename := r.URL.Path[strings.LastIndexByte(r.URL.Path, '/')+1:]
share, err := mu.Conf.GetShare(sharename)
if err != nil {
http.Error(w, fmt.Sprintf("Share '%s' not found: %s", sharename, err), http.StatusNotFound)
return
}
user, _ := getUser(r) // user is set: checked by middleware
if !share.IsAuthorized(user) {
http.Error(w, "You are not authorized to this share", http.StatusForbidden)
return
}
serialized, err := json.Marshal(share)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Header().Set("Content-type", "application/json")
w.Write(serialized)
}
// on success, redirect to view URL
func (mu *MegaUploader) upload(w http.ResponseWriter, r *http.Request) {
user, _ := getUser(r) // user is set: checked by middleware
sharename := r.URL.Path[strings.LastIndexByte(r.URL.Path, '/')+1:]
share, err := mu.Conf.GetAuthShare(sharename, user)
if err != nil {
http.Error(w, fmt.Sprintf("Share '%s' not found: %s", sharename, err), http.StatusNotFound)
return
}
sizelimit := uint64(20 * datasize.MB)
if share.SizeLimit.Bytes() > 0 {
sizelimit = share.SizeLimit.Bytes()
}
err = r.ParseMultipartForm(int64(sizelimit))
if err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "No file uploaded", http.StatusBadRequest)
return
}
fname, err := share.Upload(file, filepath.Base(header.Filename))
if err != nil {
fmt.Fprintln(os.Stderr, err)
http.Error(w, "Error uploading", http.StatusInternalServerError)
return
}
fname = filepath.Base(fname)
u, err := mu.Conf.GetShareURL(sharename)
if err != nil {
w.Write([]byte(fname))
return
}
u.Path = path.Join(u.Path, fname)
w.Write([]byte(u.String()))
}