196 lines
5.6 KiB
Go
196 lines
5.6 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/gorilla/handlers"
|
|
"github.com/gorilla/mux"
|
|
"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'; img-src 'self' data: ",
|
|
}),
|
|
}
|
|
}
|
|
|
|
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)) http.Handler {
|
|
return mu.secureMW.Handler(mu.confAcquire(requireUserMiddleware(inner)))
|
|
}
|
|
func (mu *MegaUploader) publicMiddleware(inner func(w http.ResponseWriter, r *http.Request)) http.Handler {
|
|
return mu.secureMW.Handler(requireUserMiddleware(inner))
|
|
}
|
|
|
|
// SetupRoutes adds API routes
|
|
func (mu *MegaUploader) SetupRoutes() http.Handler {
|
|
prefix := strings.TrimRight(mu.Conf.Global.RoutePrefix, "/")
|
|
root := mux.NewRouter()
|
|
r := root.PathPrefix(prefix).Subrouter()
|
|
|
|
r.Handle("/", mu.privateMiddleware(mu.home))
|
|
r.Handle("/upload/{share}", mu.privateMiddleware(mu.uploadUI))
|
|
static := rice.MustFindBox("res/static")
|
|
r.PathPrefix("/static/").Handler(mu.publicMiddleware(
|
|
http.StripPrefix(prefix+"/static/", http.FileServer(static.HTTPBox())).ServeHTTP,
|
|
))
|
|
api := r.PathPrefix("/api/").Subrouter()
|
|
api.Handle("/share", mu.privateMiddleware(mu.listShares))
|
|
api.Handle("/share/{share}", mu.privateMiddleware(mu.getShare))
|
|
api.Handle("/upload/{share}", mu.privateMiddleware(mu.upload)).Methods(http.MethodPost)
|
|
|
|
return handlers.CombinedLoggingHandler(os.Stdout, r)
|
|
}
|
|
|
|
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 := mux.Vars(r)["share"]
|
|
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 := mux.Vars(r)["share"]
|
|
|
|
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()
|
|
}
|
|
mpreader, err := r.MultipartReader()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
http.Error(w, "Bad request: error parsing form", http.StatusBadRequest)
|
|
return
|
|
}
|
|
part, err := mpreader.NextPart()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
http.Error(w, "Bad request: error reading part from multipart form", http.StatusBadRequest)
|
|
}
|
|
fname, err := share.Upload(part, filepath.Base(part.FileName()))
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
http.Error(w, "Error uploading", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
finfo, err := os.Stat(fname)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
http.Error(w, "Error uploading", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if uint64(finfo.Size()) > sizelimit {
|
|
err = os.Remove(fname)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "could not delete file exceeding size limit", err)
|
|
}
|
|
http.Error(w, "File size limit exceeded", http.StatusBadRequest)
|
|
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()))
|
|
}
|