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