123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- 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()))
- }
|