From 43126b3d921fde3e070b9b69e7bf38e0f7490898 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Mon, 13 Nov 2023 07:12:50 +0600 Subject: [PATCH 1/5] :wrench: Added static directory in config --- internal/domain/config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/domain/config.go b/internal/domain/config.go index d80c371..9072a4e 100644 --- a/internal/domain/config.go +++ b/internal/domain/config.go @@ -11,6 +11,7 @@ type Config struct { ContentDir string `env:"CONTENT_DIR" envDefault:"content/"` Host string `env:"HOST" envDefault:"0.0.0.0"` ThemeDir string `env:"THEME_DIR" envDefault:"theme/"` + StaticDir string `env:"STATIC_DIR" envDefault:"static/"` Port uint16 `env:"PORT" envDefault:"3000"` } @@ -21,6 +22,7 @@ func TestConfig(tb testing.TB) *Config { ContentDir: "testdata/content/", Host: "0.0.0.0", ThemeDir: "testdata/theme/", + StaticDir: "testdata/static/", Port: 3000, } } From 495d49dada0aa6108f7e521678c7933c5a840785 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Mon, 13 Nov 2023 07:15:39 +0600 Subject: [PATCH 2/5] :building_construction: Serve static from HOME_STATIC_DIR first --- main.go | 44 +++++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/main.go b/main.go index 52bc7ca..7dbfdbc 100644 --- a/main.go +++ b/main.go @@ -63,20 +63,23 @@ var cpuProfilePath, memProfilePath string func NewApp(ctx context.Context, config *domain.Config) (*App, error) { themeDir := os.DirFS(config.ThemeDir) contentDir := os.DirFS(config.ContentDir) - statics := staticfsrepo.NewFileServerStaticRepository(contentDir) + resources := staticfsrepo.NewFileServerStaticRepository(contentDir) sites := sitefsrepo.NewFileSystemSiteRepository(contentDir) - siter := siteucase.NewSiteUseCase(sites, statics) + siter := siteucase.NewSiteUseCase(sites, resources) funcMap, err := templateutil.New(themeDir, siter) if err != nil { logger.Fatalln("cannot setup template.FuncMap for templates: %w", err) } - staticer := staticucase.NewStaticUseCase(statics) + staticDir := os.DirFS(config.StaticDir) + statics := staticfsrepo.NewFileServerStaticRepository(staticDir) + // TODO(toby3d): use exists static use case or split that on static and resource modules? + resourcer := staticucase.NewStaticUseCase(resources) themes := themefsrepo.NewFileSystemThemeRepository(themeDir, funcMap) themer := themeucase.NewThemeUseCase(themes) pages := pagefsrepo.NewFileSystemPageRepository(contentDir) - pager := pageucase.NewPageUseCase(pages, statics) + pager := pageucase.NewPageUseCase(pages, resources) matcher := language.NewMatcher(message.DefaultCatalog.Languages()) @@ -98,32 +101,18 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) { return } - lang := domain.NewLanguage(head) - if lang == domain.LanguageUnd { - // WARN(toby3d): fetch static resources from separated static directory instead of - // $HOME_CONTENT_DIR? - // - // Looks like what current logic is insecure, because resource from private page in - // content directory '/en/page/file.jpg' by lower use case execution can be accessed - // here by URL '/page/file.jpg'. - res, err := staticer.Do(r.Context(), r.URL.Path) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - http.Error(w, err.Error(), http.StatusNotFound) - - return - } - - http.Error(w, err.Error(), http.StatusInternalServerError) - - return - } - - http.ServeContent(w, r, res.Name(), domain.ResourceModTime(res), res) + // TODO(toby3d): use exists static use case or split that on static and resource modules? + // INFO(toby3d): any static file is public by design, so it's safe to search it first before + // deep down to any page or it's resource which might be secured by middleware or something + // else. + static, err := statics.Get(r.Context(), r.URL.Path[1:]) + if err == nil { + http.ServeContent(w, r, static.Name(), domain.ResourceModTime(static), static) return } + lang := domain.NewLanguage(head) r.URL.Path = tail s, err := siter.Do(r.Context(), lang) @@ -141,7 +130,7 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) { return } - res, err := staticer.Do(r.Context(), r.URL.Path) + res, err := resourcer.Do(r.Context(), r.URL.Path) if err != nil { if errors.Is(err, fs.ErrNotExist) { http.Error(w, err.Error(), http.StatusNotFound) @@ -225,6 +214,7 @@ func main() { for _, dir := range []*string{ &config.ContentDir, &config.ThemeDir, + &config.StaticDir, } { if *dir, err = filepath.Abs(filepath.Clean(*dir)); err != nil { logger.Fatalf("cannot format '%s' into absolute path: %s", *dir, err) From f8a6580444b7446918294d3d6bbd9c7acf9eed3d Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Mon, 13 Nov 2023 07:15:59 +0600 Subject: [PATCH 3/5] :whale: Expose default static directory --- build/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index 0bd4cef..c1ce97c 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -23,7 +23,7 @@ WORKDIR / COPY --from=builder /app/home /home -VOLUME ["/content", "/theme"] +VOLUME ["/content", "/theme", "/static"] EXPOSE 3000 From 6fd916058d86ed2e59bfdba4d8a095e00a4759a5 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Mon, 13 Nov 2023 07:17:41 +0600 Subject: [PATCH 4/5] :recycle: Safe strip slash prefix for static path --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 7dbfdbc..998a2ec 100644 --- a/main.go +++ b/main.go @@ -105,7 +105,7 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) { // INFO(toby3d): any static file is public by design, so it's safe to search it first before // deep down to any page or it's resource which might be secured by middleware or something // else. - static, err := statics.Get(r.Context(), r.URL.Path[1:]) + static, err := statics.Get(r.Context(), strings.TrimPrefix(r.URL.Path, "/")) if err == nil { http.ServeContent(w, r, static.Name(), domain.ResourceModTime(static), static) From 90a91c332de6eaa0dfdcbd7ecb9a046060f405f8 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Mon, 13 Nov 2023 07:21:45 +0600 Subject: [PATCH 5/5] :pencil2: Updated doc comment about static serving --- main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 998a2ec..7f39afa 100644 --- a/main.go +++ b/main.go @@ -102,9 +102,9 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) { } // TODO(toby3d): use exists static use case or split that on static and resource modules? - // INFO(toby3d): any static file is public by design, so it's safe to search it first before - // deep down to any page or it's resource which might be secured by middleware or something - // else. + // INFO(toby3d): any static file is public and unprotected by design, so it's safe to search it + // first before deep down to any page or it's resource which might be secured by middleware or + // something else. static, err := statics.Get(r.Context(), strings.TrimPrefix(r.URL.Path, "/")) if err == nil { http.ServeContent(w, r, static.Name(), domain.ResourceModTime(static), static)