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