diff --git a/.drone.yml b/.drone.yml index 65cbc355..253ecdf5 100644 --- a/.drone.yml +++ b/.drone.yml @@ -26,11 +26,10 @@ steps: - apk add make git > /dev/null - npm install --silent - make build_frontend - - cp -R ./dist/data/public ./filestash/data/public - name: build_backend image: golang:1.16-stretch - depends_on: [ build_prepare ] + depends_on: [ build_prepare, build_frontend ] environment: CGO_LDFLAGS_ALLOW: "-fopenmp" GO111MODULE: "on" diff --git a/.gitignore b/.gitignore index aa1e733c..f606489e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ package-lock.json .tern-port .tern-project.js *_test.go -cover.* \ No newline at end of file +cover.* +www \ No newline at end of file diff --git a/package.json b/package.json index e4a5dea2..6d0ef980 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "repository": "https://github.com/mickael-kerjean/filestash", "main": "server/index.js", "scripts": { - "dev": "webpack --watch", + "start": "webpack --watch", "build": "webpack", "test": "jest ./client/", "lint": "eslint ./client/" diff --git a/server/common/constants.go b/server/common/constants.go index 46fafb3a..00fd403d 100644 --- a/server/common/constants.go +++ b/server/common/constants.go @@ -19,8 +19,6 @@ const ( COOKIE_NAME_ADMIN = "admin" COOKIE_PATH_ADMIN = "/admin/api/" COOKIE_PATH = "/api/" - FILE_INDEX = "./data/public/index.html" - FILE_ASSETS = "./data/public/" URL_SETUP = "/admin/setup" ) diff --git a/server/ctrl/static.go b/server/ctrl/static.go index bd28a173..2673811d 100644 --- a/server/ctrl/static.go +++ b/server/ctrl/static.go @@ -1,7 +1,7 @@ package ctrl import ( - _ "embed" + "embed" "fmt" . "github.com/mickael-kerjean/filestash/server/common" "io" @@ -14,46 +14,49 @@ import ( "text/template" ) -//go:embed static/404.html -var HtmlPage404 []byte +var ( + //go:embed static/www + WWWEmbed embed.FS + + //go:embed static/404.html + HtmlPage404 []byte +) func StaticHandler(_path string) func(*App, http.ResponseWriter, *http.Request) { return func(ctx *App, res http.ResponseWriter, req *http.Request) { - var base string = GetAbsolutePath(_path) - var srcPath string - if srcPath = JoinPath(base, req.URL.Path); srcPath == base { + var chroot string = GetAbsolutePath(_path) + if srcPath := JoinPath(chroot, req.URL.Path); strings.HasPrefix(srcPath, chroot) == false { http.NotFound(res, req) return } - ServeFile(res, req, srcPath) + ServeFile(res, req, JoinPath(_path, req.URL.Path)) } } -func IndexHandler(_path string) func(*App, http.ResponseWriter, *http.Request) { - return func(ctx *App, res http.ResponseWriter, req *http.Request) { - urlObj, err := URL.Parse(req.URL.String()) - if err != nil { - NotFoundHandler(ctx, res, req) - return - } - url := urlObj.Path +func IndexHandler(ctx *App, res http.ResponseWriter, req *http.Request) { + urlObj, err := URL.Parse(req.URL.String()) + if err != nil { + NotFoundHandler(ctx, res, req) + return + } + url := urlObj.Path - if url != URL_SETUP && Config.Get("auth.admin").String() == "" { - http.Redirect(res, req, URL_SETUP, http.StatusTemporaryRedirect) - return - } else if url != "/" && strings.HasPrefix(url, "/s/") == false && - strings.HasPrefix(url, "/view/") == false && strings.HasPrefix(url, "/files/") == false && - url != "/login" && url != "/logout" && strings.HasPrefix(url, "/admin") == false && strings.HasPrefix(url, "/tags") == false { - NotFoundHandler(ctx, res, req) - return - } - ua := req.Header.Get("User-Agent") - if strings.Contains(ua, "MSIE ") || strings.Contains(ua, "Trident/") || strings.Contains(ua, "Edge/") { - // Microsoft is behaving on many occasion differently than Firefox / Chrome. - // I have neither the time / motivation for it to work properly - res.WriteHeader(http.StatusBadRequest) - res.Write([]byte( - Page(` + if url != URL_SETUP && Config.Get("auth.admin").String() == "" { + http.Redirect(res, req, URL_SETUP, http.StatusTemporaryRedirect) + return + } else if url != "/" && strings.HasPrefix(url, "/s/") == false && + strings.HasPrefix(url, "/view/") == false && strings.HasPrefix(url, "/files/") == false && + url != "/login" && url != "/logout" && strings.HasPrefix(url, "/admin") == false && strings.HasPrefix(url, "/tags") == false { + NotFoundHandler(ctx, res, req) + return + } + ua := req.Header.Get("User-Agent") + if strings.Contains(ua, "MSIE ") || strings.Contains(ua, "Trident/") || strings.Contains(ua, "Edge/") { + // Microsoft is behaving on many occasion differently than Firefox / Chrome. + // I have neither the time / motivation for it to work properly + res.WriteHeader(http.StatusBadRequest) + res.Write([]byte( + Page(`

Internet explorer is not supported

We don't support IE / Edge at this time @@ -61,11 +64,9 @@ func IndexHandler(_path string) func(*App, http.ResponseWriter, *http.Request) { Please use either Chromium, Firefox or Chrome

`))) - return - } - srcPath := GetAbsolutePath(_path) - ServeFile(res, req, srcPath) + return } + ServeFile(res, req, "/index.html") } func NotFoundHandler(ctx *App, res http.ResponseWriter, req *http.Request) { @@ -130,6 +131,14 @@ func AboutHandler(ctx *App, res http.ResponseWriter, req *http.Request) { table a { color: inherit; text-decoration: none; } `)) + hashFileContent := func(path string, n int) string { + f, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm) + if err != nil { + return "" + } + defer f.Close() + return HashStream(f, n) + } t.Execute(res, struct { Version string CommitHash string @@ -219,75 +228,43 @@ func CustomCssHandler(ctx *App, res http.ResponseWriter, req *http.Request) { } func ServeFile(res http.ResponseWriter, req *http.Request, filePath string) { - zFilePath := filePath + ".gz" - bFilePath := filePath + ".br" - - etagNormal := hashFile(filePath, 10) - etagGzip := hashFile(zFilePath, 10) - etagBr := hashFile(bFilePath, 10) - - if req.Header.Get("If-None-Match") != "" { - browserTag := req.Header.Get("If-None-Match") - if browserTag == etagNormal { - res.WriteHeader(http.StatusNotModified) - return - } else if browserTag == etagBr { - res.WriteHeader(http.StatusNotModified) - return - } else if browserTag == etagGzip { - res.WriteHeader(http.StatusNotModified) - return - } + staticConfig := []struct { + ContentType string + FileExt string + }{ + {"br", ".br"}, + {"gzip", ".gz"}, + {"", ""}, } + head := res.Header() acceptEncoding := req.Header.Get("Accept-Encoding") - if strings.Contains(acceptEncoding, "br") { - if file, err := os.OpenFile(bFilePath, os.O_RDONLY, os.ModePerm); err == nil { - head.Set("Content-Encoding", "br") - head.Set("Etag", etagBr) - io.Copy(res, file) - file.Close() - return + for _, cfg := range staticConfig { + if strings.Contains(acceptEncoding, cfg.ContentType) == false { + continue } - } else if strings.Contains(acceptEncoding, "gzip") { - if file, err := os.OpenFile(zFilePath, os.O_RDONLY, os.ModePerm); err == nil { - head.Set("Content-Encoding", "gzip") - head.Set("Etag", etagGzip) - io.Copy(res, file) - file.Close() - return + curPath := filePath + cfg.FileExt + file, err := WWWEmbed.Open("static/www" + curPath) + if err != nil { + continue } - } - - file, err := os.OpenFile(filePath, os.O_RDONLY, os.ModePerm) - if err != nil { - http.NotFound(res, req) + if stat, err := file.Stat(); err == nil { + etag := QuickHash(fmt.Sprintf( + "%s %d %d %s", + curPath, stat.Size(), stat.Mode(), stat.ModTime()), 10, + ) + if etag == req.Header.Get("If-None-Match") { + res.WriteHeader(http.StatusNotModified) + return + } + head.Set("Etag", etag) + } + if cfg.ContentType != "" { + head.Set("Content-Encoding", cfg.ContentType) + } + io.Copy(res, file) + file.Close() return } - head.Set("Etag", etagNormal) - io.Copy(res, file) - file.Close() -} - -func hashFile(path string, n int) string { - f, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm) - if err != nil { - return "" - } - defer f.Close() - - stat, err := f.Stat() - if err != nil { - return "" - } - return QuickHash(fmt.Sprintf("%s %d %d %s", path, stat.Size(), stat.Mode(), stat.ModTime()), n) -} - -func hashFileContent(path string, n int) string { - f, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm) - if err != nil { - return "" - } - defer f.Close() - return HashStream(f, n) + http.NotFound(res, req) } diff --git a/server/main.go b/server/main.go index 8736c658..3fb381b8 100644 --- a/server/main.go +++ b/server/main.go @@ -85,7 +85,7 @@ func Init(a App) { // Webdav server / Shared Link middlewares = []Middleware{IndexHeaders, SecureHeaders} - r.HandleFunc("/s/{share}", NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, a)).Methods("GET") + r.HandleFunc("/s/{share}", NewMiddlewareChain(IndexHandler, middlewares, a)).Methods("GET") middlewares = []Middleware{WebdavBlacklist, SessionStart} r.PathPrefix("/s/{share}").Handler(NewMiddlewareChain(WebdavHandler, middlewares, a)) middlewares = []Middleware{ApiHeaders, SecureHeaders, RedirectSharedLoginIfNeeded, SessionStart, LoggedInOnly} @@ -97,9 +97,9 @@ func Init(a App) { r.HandleFunc("/api/backend", NewMiddlewareChain(AdminBackend, middlewares, a)).Methods("GET") r.HandleFunc("/api/middlewares/authentication", NewMiddlewareChain(AdminAuthenticationMiddleware, middlewares, a)).Methods("GET") middlewares = []Middleware{StaticHeaders, SecureHeaders} - r.PathPrefix("/assets").Handler(http.HandlerFunc(NewMiddlewareChain(StaticHandler(FILE_ASSETS), middlewares, a))).Methods("GET") - r.HandleFunc("/favicon.ico", NewMiddlewareChain(StaticHandler(FILE_ASSETS+"/assets/logo/"), middlewares, a)).Methods("GET") - r.HandleFunc("/sw_cache.js", NewMiddlewareChain(StaticHandler(FILE_ASSETS+"/assets/worker/"), middlewares, a)).Methods("GET") + r.PathPrefix("/assets").Handler(http.HandlerFunc(NewMiddlewareChain(StaticHandler("/"), middlewares, a))).Methods("GET") + r.HandleFunc("/favicon.ico", NewMiddlewareChain(StaticHandler("/assets/logo/"), middlewares, a)).Methods("GET") + r.HandleFunc("/sw_cache.js", NewMiddlewareChain(StaticHandler("/assets/worker/"), middlewares, a)).Methods("GET") // Other endpoints middlewares = []Middleware{ApiHeaders} @@ -118,8 +118,8 @@ func Init(a App) { } initPluginsRoutes(r, &a) - r.PathPrefix("/admin").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, a))).Methods("GET") - r.PathPrefix("/").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, a))).Methods("GET", "POST") + r.PathPrefix("/admin").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler, middlewares, a))).Methods("GET") + r.PathPrefix("/").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler, middlewares, a))).Methods("GET", "POST") // Routes are served via plugins to avoid getting stuck with plain HTTP. The idea is to // support many more protocols in the future: HTTPS, HTTP2, TOR or whatever that sounds diff --git a/webpack.config.js b/webpack.config.js index c56d21d6..3b75318b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -11,7 +11,7 @@ const config = { app: path.join(__dirname, "client", "index.js"), }, output: { - path: path.join(__dirname, "dist", "data", "public"), + path: path.join(__dirname, "server", "ctrl", "static", "www"), publicPath: "/", filename: "assets/js/[name]_[chunkhash].js", chunkFilename: "assets/js/chunk_[name]_[id]_[chunkhash].js",