diff --git a/cmd/fpb/fpb_test.go b/cmd/fpb/fpb_x_test.go similarity index 100% rename from cmd/fpb/fpb_test.go rename to cmd/fpb/fpb_x_test.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 6c3ecb0..0e05ff6 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -15,21 +15,38 @@ import ( "github.com/chriskuehl/fluffy/server" "github.com/chriskuehl/fluffy/server/config" + "github.com/chriskuehl/fluffy/server/config/loader" "github.com/chriskuehl/fluffy/server/logging" ) var Version = "(dev)" -func newConfigFromArgs(args []string) (*config.Config, error) { - c := server.NewConfig() +type cmdConfig struct { + conf *config.Config + + printConfig bool + configPath string +} + +func newConfigFromArgs(args []string) (*cmdConfig, error) { + c := &cmdConfig{ + conf: server.NewConfig(), + } fs := flag.NewFlagSet("fluffy", flag.ExitOnError) - fs.StringVar(&c.Host, "host", "localhost", "host to listen on") - fs.UintVar(&c.Port, "port", 8080, "port to listen on") - fs.BoolVar(&c.DevMode, "dev", false, "enable dev mode") + fs.BoolVar(&c.printConfig, "print-config", false, "print the config and exit") + fs.StringVar(&c.configPath, "config", "", "path to config file") + fs.StringVar(&c.conf.Host, "host", "localhost", "host to listen on") + fs.UintVar(&c.conf.Port, "port", 8080, "port to listen on") + fs.BoolVar(&c.conf.DevMode, "dev", false, "enable dev mode") if err := fs.Parse(args); err != nil { return nil, err } - c.Version = Version + if c.configPath != "" { + if err := loader.LoadConfigTOML(c.conf, c.configPath); err != nil { + return nil, fmt.Errorf("loading config: %w", err) + } + } + c.conf.Version = Version return c, nil } @@ -37,25 +54,35 @@ func run(ctx context.Context, w io.Writer, args []string) error { ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) defer cancel() - config, err := newConfigFromArgs(args) + conf, err := newConfigFromArgs(args) if err != nil { return fmt.Errorf("parsing args: %w", err) } + if conf.printConfig { + c, err := loader.DumpConfigTOML(conf.conf) + if err != nil { + return fmt.Errorf("dumping config: %w", err) + } + + fmt.Println(c) + return nil + } + logger := logging.NewSlogLogger(slog.New(slog.NewTextHandler(w, nil))) - handler, err := server.NewServer(logger, config) + handler, err := server.NewServer(logger, conf.conf) if err != nil { return fmt.Errorf("creating server: %w", err) } httpServer := &http.Server{ - Addr: net.JoinHostPort(config.Host, strconv.FormatUint(uint64(config.Port), 10)), + Addr: net.JoinHostPort(conf.conf.Host, strconv.FormatUint(uint64(conf.conf.Port), 10)), Handler: handler, } go func() { logger.Info(ctx, "listening", "addr", httpServer.Addr) - if config.DevMode { + if conf.conf.DevMode { logger.Warn(ctx, "dev mode enabled! do not use in production!") } if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { diff --git a/cmd/server/main_test.go b/cmd/server/main_x_test.go similarity index 100% rename from cmd/server/main_test.go rename to cmd/server/main_x_test.go diff --git a/go.mod b/go.mod index 8f53678..a9a64bf 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( ) require ( + github.com/BurntSushi/toml v1.4.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/sys v0.24.0 // indirect diff --git a/go.sum b/go.sum index 9cb4e3b..9171084 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY= github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= diff --git a/server/assets/assets.go b/server/assets/assets.go index 9e74b7e..1cd51e0 100644 --- a/server/assets/assets.go +++ b/server/assets/assets.go @@ -68,9 +68,9 @@ func assetObjectPath(path, hash string) string { // // In development mode, this will return a URL served by the fluffy server itself. In production, // this will return a URL to the object store. -func AssetURL(c *config.Config, path string) (string, error) { - if c.DevMode { - url := c.HomeURL +func AssetURL(conf *config.Config, path string) (string, error) { + if conf.DevMode { + url := conf.HomeURL url.Path = "/dev/static/" + path return url.String(), nil } @@ -79,8 +79,8 @@ func AssetURL(c *config.Config, path string) (string, error) { if !ok { return "", fmt.Errorf("asset not found: %s", path) } - url := c.ObjectURLPattern - url.Path = fmt.Sprintf(url.Path, assetObjectPath(path, hash)) + url := conf.ObjectURLPattern + url.Path = strings.Replace(url.Path, "{path}", assetObjectPath(path, hash), -1) return url.String(), nil } diff --git a/server/assets/assets_test.go b/server/assets/assets_test.go new file mode 100644 index 0000000..14f7021 --- /dev/null +++ b/server/assets/assets_test.go @@ -0,0 +1,36 @@ +package assets_test + +import ( + "testing" + + "github.com/chriskuehl/fluffy/server/assets" + "github.com/chriskuehl/fluffy/testfunc" +) + +func TestAssetURLDev(t *testing.T) { + conf := testfunc.NewConfig() + conf.DevMode = true + + got, err := assets.AssetURL(conf, "img/favicon.ico") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + want := "http://localhost:8080/dev/static/img/favicon.ico" + if got != want { + t.Fatalf("got %q, want %q", got, want) + } +} + +func TestAssetURLProd(t *testing.T) { + conf := testfunc.NewConfig() + conf.DevMode = false + + got, err := assets.AssetURL(conf, "img/favicon.ico") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + want := "http://localhost:8080/dev/object/static/5b707398fe549635b8794ac8e73db6938dd7b6b7a28b339296bde1b0fdec764b/img/favicon.ico" + if got != want { + t.Fatalf("got %q, want %q", got, want) + } +} diff --git a/server/assets/dev.go b/server/assets/dev.go index 69d7252..58d3423 100644 --- a/server/assets/dev.go +++ b/server/assets/dev.go @@ -8,8 +8,8 @@ import ( "github.com/chriskuehl/fluffy/server/logging" ) -func HandleDevStatic(config *config.Config, logger logging.Logger) http.HandlerFunc { - if !config.DevMode { +func HandleDevStatic(conf *config.Config, logger logging.Logger) http.HandlerFunc { + if !conf.DevMode { return func(w http.ResponseWriter, r *http.Request) { logger.Warn(r.Context(), "assets cannot be served from the server in production") w.WriteHeader(http.StatusNotFound) diff --git a/server/config/config.go b/server/config/config.go index d76ab82..81960c5 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -35,35 +35,38 @@ type Config struct { Version string } -func (c *Config) Validate() []string { +func (conf *Config) Validate() []string { var errs []string - if c.Branding == "" { + if conf.Branding == "" { errs = append(errs, "Branding must not be empty") } - if c.AbuseContactEmail == "" { + if conf.AbuseContactEmail == "" { errs = append(errs, "AbuseContactEmail must not be empty") } - if c.MaxUploadBytes <= 0 { + if conf.MaxUploadBytes <= 0 { errs = append(errs, "MaxUploadBytes must be greater than 0") } - if c.MaxMultipartMemoryBytes <= 0 { + if conf.MaxMultipartMemoryBytes <= 0 { errs = append(errs, "MaxMultipartMemoryBytes must be greater than 0") } - if strings.HasSuffix(c.HomeURL.Path, "/") { + if strings.HasSuffix(conf.HomeURL.Path, "/") { errs = append(errs, "HomeURL must not end with a slash") } - if !strings.Contains(c.ObjectURLPattern.Path, "%s") { - errs = append(errs, "ObjectURLPattern must contain a '%s' placeholder") + if !strings.Contains(conf.ObjectURLPattern.Path, "{path}") { + errs = append(errs, "ObjectURLPattern must contain a '{path}' placeholder") } - if !strings.Contains(c.HTMLURLPattern.Path, "%s") { - errs = append(errs, "HTMLURLPattern must contain a '%s' placeholder") + if !strings.Contains(conf.HTMLURLPattern.Path, "{path}") { + errs = append(errs, "HTMLURLPattern must contain a '{path}' placeholder") } - for ext := range c.ForbiddenFileExtensions { + if conf.ForbiddenFileExtensions == nil { + errs = append(errs, "ForbiddenFileExtensions must not be nil") + } + for ext := range conf.ForbiddenFileExtensions { if strings.HasPrefix(ext, ".") { errs = append(errs, "ForbiddenFileExtensions should not start with a dot: "+ext) } } - if c.Version == "" { + if conf.Version == "" { errs = append(errs, "Version must not be empty") } return errs diff --git a/server/config/loader/loader.go b/server/config/loader/loader.go new file mode 100644 index 0000000..25567a0 --- /dev/null +++ b/server/config/loader/loader.go @@ -0,0 +1,125 @@ +package loader + +import ( + "fmt" + "html/template" + "net/url" + + "github.com/BurntSushi/toml" + "github.com/chriskuehl/fluffy/server/config" + "github.com/chriskuehl/fluffy/server/storage" +) + +type filesystemStorageBackend struct { + ObjectRoot string `toml:"object_root"` + HTMLRoot string `toml:"html_root"` +} + +type configFile struct { + Branding string `toml:"branding"` + CustomFooterHTML string `toml:"custom_footer_html"` + AbuseContactEmail string `toml:"abuse_contact_email"` + MaxUploadBytes int64 `toml:"max_upload_bytes"` + MaxMultipartMemoryBytes int64 `toml:"max_multipart_memory_bytes"` + HomeURL string `toml:"home_url"` + ObjectURLPattern string `toml:"object_url_pattern"` + HTMLURLPattern string `toml:"html_url_pattern"` + ForbiddenFileExtensions []string `toml:"forbidden_file_extensions"` + Host string `toml:"host"` + Port uint `toml:"port"` + + FilesystemStorageBackend *filesystemStorageBackend `toml:"filesystem_storage_backend"` +} + +func LoadConfigTOML(conf *config.Config, path string) error { + var cfg configFile + md, err := toml.DecodeFile(path, &cfg) + if err != nil { + return fmt.Errorf("decoding config: %w", err) + } + if len(md.Undecoded()) > 0 { + return fmt.Errorf("unknown keys in config: %v", md.Undecoded()) + } + if cfg.Branding != "" { + conf.Branding = cfg.Branding + } + if cfg.CustomFooterHTML != "" { + conf.CustomFooterHTML = template.HTML(cfg.CustomFooterHTML) + } + if cfg.AbuseContactEmail != "" { + conf.AbuseContactEmail = cfg.AbuseContactEmail + } + if cfg.MaxUploadBytes != 0 { + conf.MaxUploadBytes = cfg.MaxUploadBytes + } + if cfg.MaxMultipartMemoryBytes != 0 { + conf.MaxMultipartMemoryBytes = cfg.MaxMultipartMemoryBytes + } + if cfg.HomeURL != "" { + u, err := url.ParseRequestURI(cfg.HomeURL) + if err != nil { + return fmt.Errorf("parsing HomeURL: %w", err) + } + conf.HomeURL = *u + } + if cfg.ObjectURLPattern != "" { + u, err := url.ParseRequestURI(cfg.ObjectURLPattern) + if err != nil { + return fmt.Errorf("parsing ObjectURLPattern: %w", err) + } + conf.ObjectURLPattern = *u + } + if cfg.HTMLURLPattern != "" { + u, err := url.ParseRequestURI(cfg.HTMLURLPattern) + if err != nil { + return fmt.Errorf("parsing HTMLURLPattern: %w", err) + } + conf.HTMLURLPattern = *u + } + for _, ext := range cfg.ForbiddenFileExtensions { + conf.ForbiddenFileExtensions[ext] = struct{}{} + } + if cfg.Host != "" { + conf.Host = cfg.Host + } + if cfg.Port != 0 { + conf.Port = cfg.Port + } + if cfg.FilesystemStorageBackend != nil { + conf.StorageBackend = &storage.FilesystemBackend{ + ObjectRoot: cfg.FilesystemStorageBackend.ObjectRoot, + HTMLRoot: cfg.FilesystemStorageBackend.HTMLRoot, + } + } + return nil +} + +func DumpConfigTOML(conf *config.Config) (string, error) { + cfg := configFile{ + Branding: conf.Branding, + CustomFooterHTML: string(conf.CustomFooterHTML), + AbuseContactEmail: conf.AbuseContactEmail, + MaxUploadBytes: conf.MaxUploadBytes, + MaxMultipartMemoryBytes: conf.MaxMultipartMemoryBytes, + HomeURL: conf.HomeURL.String(), + ObjectURLPattern: conf.ObjectURLPattern.String(), + HTMLURLPattern: conf.HTMLURLPattern.String(), + ForbiddenFileExtensions: make([]string, 0, len(conf.ForbiddenFileExtensions)), + Host: conf.Host, + Port: conf.Port, + } + for ext := range conf.ForbiddenFileExtensions { + cfg.ForbiddenFileExtensions = append(cfg.ForbiddenFileExtensions, ext) + } + if fs, ok := conf.StorageBackend.(*storage.FilesystemBackend); ok { + cfg.FilesystemStorageBackend = &filesystemStorageBackend{ + ObjectRoot: fs.ObjectRoot, + HTMLRoot: fs.HTMLRoot, + } + } + buf, err := toml.Marshal(cfg) + if err != nil { + return "", fmt.Errorf("marshaling config: %w", err) + } + return string(buf), nil +} diff --git a/server/config/loader/loader_test.go b/server/config/loader/loader_test.go new file mode 100644 index 0000000..3a67e2b --- /dev/null +++ b/server/config/loader/loader_test.go @@ -0,0 +1,84 @@ +package loader + +import ( + "net/url" + "os" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/chriskuehl/fluffy/server/config" + "github.com/chriskuehl/fluffy/server/storage" + "github.com/chriskuehl/fluffy/testfunc" +) + +func TestLoadConfigTOMLEmptyFile(t *testing.T) { + configPath := t.TempDir() + "/config.toml" + if err := os.WriteFile(configPath, []byte(""), 0644); err != nil { + t.Fatalf("failed to write file: %v", err) + } + + conf := testfunc.NewConfig() + if err := LoadConfigTOML(conf, configPath); err != nil { + t.Fatalf("failed to load config: %v", err) + } + errs := conf.Validate() + if len(errs) != 0 { + t.Fatalf("config validation failed: %v", errs) + } +} + +func TestLoadConfigTOMLWithEverything(t *testing.T) { + configPath := t.TempDir() + "/config.toml" + if err := os.WriteFile(configPath, []byte(` +branding = "foo" +custom_footer_html = "

foo

" +abuse_contact_email = "abuse@foo.com" +max_upload_bytes = 123 +max_multipart_memory_bytes = 456 +home_url = "http://foo.com" +object_url_pattern = "http://i.foo.com/o/{path}" +html_url_pattern = "http://i.foo.com/h/{path}" +forbidden_file_extensions = ["foo", "bar"] +host = "192.168.1.100" +port = 5555 + +[filesystem_storage_backend] +object_root = "/tmp/objects" +html_root = "/tmp/html" +`), 0644); err != nil { + t.Fatalf("failed to write file: %v", err) + } + + conf := testfunc.NewConfig() + if err := LoadConfigTOML(conf, configPath); err != nil { + t.Fatalf("failed to load config: %v", err) + } + + errs := conf.Validate() + if len(errs) != 0 { + t.Fatalf("config validation failed: %v", errs) + } + + want := &config.Config{ + Branding: "foo", + CustomFooterHTML: "

foo

", + AbuseContactEmail: "abuse@foo.com", + MaxUploadBytes: 123, + MaxMultipartMemoryBytes: 456, + HomeURL: url.URL{Scheme: "http", Host: "foo.com"}, + ObjectURLPattern: url.URL{Scheme: "http", Host: "i.foo.com", Path: "/o/{path}", RawPath: "/o/{path}"}, + HTMLURLPattern: url.URL{Scheme: "http", Host: "i.foo.com", Path: "/h/{path}", RawPath: "/h/{path}"}, + ForbiddenFileExtensions: map[string]struct{}{"foo": {}, "bar": {}}, + Host: "192.168.1.100", + Port: 5555, + StorageBackend: &storage.FilesystemBackend{ + ObjectRoot: "/tmp/objects", + HTMLRoot: "/tmp/html", + }, + Version: "(test)", + } + if diff := cmp.Diff(want, conf); diff != "" { + t.Fatalf("config mismatch (-want +got):\n%s", diff) + } +} diff --git a/server/meta.go b/server/meta.go index 0250f8a..d15bde1 100644 --- a/server/meta.go +++ b/server/meta.go @@ -10,20 +10,20 @@ import ( ) type meta struct { - Config *config.Config - PageConfig pageConfig - Nonce string + Conf *config.Config + PageConf pageConfig + Nonce string } -func NewMeta(ctx context.Context, config *config.Config, pc pageConfig) meta { +func NewMeta(ctx context.Context, conf *config.Config, pc pageConfig) meta { nonce, ok := ctx.Value(cspNonceKey{}).(string) if !ok { panic("no nonce in context") } return meta{ - Config: config, - PageConfig: pc, - Nonce: nonce, + Conf: conf, + PageConf: pc, + Nonce: nonce, } } @@ -50,7 +50,7 @@ func (m meta) InlineJS(path string) template.HTML { } func (m meta) AssetURL(path string) string { - url, err := assets.AssetURL(m.Config, path) + url, err := assets.AssetURL(m.Conf, path) if err != nil { panic("loading asset: " + err.Error()) } diff --git a/server/server.go b/server/server.go index 127513c..7dd7741 100644 --- a/server/server.go +++ b/server/server.go @@ -29,8 +29,9 @@ func NewConfig() *config.Config { MaxUploadBytes: 1024 * 1024 * 10, // 10 MiB MaxMultipartMemoryBytes: 1024 * 1024 * 10, // 10 MiB HomeURL: url.URL{Scheme: "http", Host: "localhost:8080"}, - ObjectURLPattern: url.URL{Scheme: "http", Host: "localhost:8080", Path: "/dev/object/%s"}, - HTMLURLPattern: url.URL{Scheme: "http", Host: "localhost:8080", Path: "/dev/html/%s"}, + ObjectURLPattern: url.URL{Scheme: "http", Host: "localhost:8080", Path: "/dev/object/{path}"}, + HTMLURLPattern: url.URL{Scheme: "http", Host: "localhost:8080", Path: "/dev/html/{path}"}, + ForbiddenFileExtensions: make(map[string]struct{}), Host: "127.0.0.1", Port: 8080, } @@ -46,8 +47,8 @@ func handleHealthz(logger logging.Logger) http.HandlerFunc { type cspNonceKey struct{} -func newCSPMiddleware(config *config.Config, next http.Handler) http.Handler { - objectURLBase := config.ObjectURLPattern +func newCSPMiddleware(conf *config.Config, next http.Handler) http.Handler { + objectURLBase := conf.ObjectURLPattern objectURLBase.Path = "" return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -69,39 +70,39 @@ func newCSPMiddleware(config *config.Config, next http.Handler) http.Handler { func addRoutes( mux *http.ServeMux, - config *config.Config, + conf *config.Config, logger logging.Logger, ) error { mux.HandleFunc("GET /healthz", handleHealthz(logger)) - if handler, err := handleIndex(config, logger); err != nil { + if handler, err := handleIndex(conf, logger); err != nil { return fmt.Errorf("handleIndex: %w", err) } else { mux.Handle("GET /{$}", handler) } - if handler, err := handleUploadHistory(config, logger); err != nil { + if handler, err := handleUploadHistory(conf, logger); err != nil { return fmt.Errorf("handleUploadHistory: %w", err) } else { mux.Handle("GET /upload-history", handler) } - mux.Handle("POST /upload", handleUpload(config, logger)) - mux.Handle("GET /dev/static/", assets.HandleDevStatic(config, logger)) - mux.Handle("GET /dev/storage/{type}/", storage.HandleDevStorage(config, logger)) + mux.Handle("POST /upload", handleUpload(conf, logger)) + mux.Handle("GET /dev/static/", assets.HandleDevStatic(conf, logger)) + mux.Handle("GET /dev/storage/{type}/", storage.HandleDevStorage(conf, logger)) return nil } func NewServer( logger logging.Logger, - config *config.Config, + conf *config.Config, ) (http.Handler, error) { - if errs := config.Validate(); len(errs) > 0 { + if errs := conf.Validate(); len(errs) > 0 { return nil, errors.New("invalid config: " + strings.Join(errs, ", ")) } mux := http.NewServeMux() - if err := addRoutes(mux, config, logger); err != nil { + if err := addRoutes(mux, conf, logger); err != nil { return nil, fmt.Errorf("adding routes: %w", err) } var handler http.Handler = mux - handler = newCSPMiddleware(config, handler) + handler = newCSPMiddleware(conf, handler) handler = logging.NewMiddleware(logger, handler) handler = http.TimeoutHandler(handler, time.Second/2, "timeout") return handler, nil diff --git a/server/storage/dev.go b/server/storage/dev.go index b85f14b..5586075 100644 --- a/server/storage/dev.go +++ b/server/storage/dev.go @@ -8,8 +8,8 @@ import ( "github.com/chriskuehl/fluffy/server/logging" ) -func HandleDevStorage(config *config.Config, logger logging.Logger) http.HandlerFunc { - if !config.DevMode { +func HandleDevStorage(conf *config.Config, logger logging.Logger) http.HandlerFunc { + if !conf.DevMode { return func(w http.ResponseWriter, r *http.Request) { logger.Warn(r.Context(), "storage cannot be served from the server in production") w.WriteHeader(http.StatusNotFound) @@ -17,7 +17,7 @@ func HandleDevStorage(config *config.Config, logger logging.Logger) http.Handler } } - storageBackend, ok := config.StorageBackend.(*FilesystemBackend) + storageBackend, ok := conf.StorageBackend.(*FilesystemBackend) if !ok { return func(w http.ResponseWriter, r *http.Request) { logger.Error(r.Context(), "storage cannot be served from the server in dev mode if not using the filesystem backend") diff --git a/server/templates/include/base.html b/server/templates/include/base.html index 6f144f7..f88745b 100644 --- a/server/templates/include/base.html +++ b/server/templates/include/base.html @@ -1,10 +1,10 @@ - + - {{.Meta.Config.Branding}} + {{.Meta.Conf.Branding}} @@ -20,21 +20,21 @@
-

{{.Meta.Config.Branding}}

+

{{.Meta.Conf.Branding}}

{{template "content" .}}
- {{if .Meta.Config.CustomFooterHTML}} + {{if .Meta.Conf.CustomFooterHTML}} {{end}} {{template "inlineJS" .}} diff --git a/server/templates/index.html b/server/templates/index.html index fd7f0aa..274c656 100644 --- a/server/templates/index.html +++ b/server/templates/index.html @@ -85,7 +85,7 @@

your recent uploads

{{.Meta.InlineJS "js/home-inline.js"}} diff --git a/server/uploads/uploads.go b/server/uploads/uploads.go index d55584d..0b1399e 100644 --- a/server/uploads/uploads.go +++ b/server/uploads/uploads.go @@ -93,13 +93,13 @@ func SanitizeUploadName(name string, forbiddenExtensions map[string]struct{}) (* func UploadObjects( ctx context.Context, logger logging.Logger, - config *config.Config, + conf *config.Config, objs []storagedata.Object, ) []error { results := make(chan error, len(objs)) for _, obj := range objs { go func() { - err := config.StorageBackend.StoreObject(ctx, obj) + err := conf.StorageBackend.StoreObject(ctx, obj) if err != nil { logger.Error(ctx, "storing object", "obj", obj, "error", err) } else { diff --git a/server/views.go b/server/views.go index 60b40a1..489edd8 100644 --- a/server/views.go +++ b/server/views.go @@ -34,10 +34,10 @@ func pageTemplate(name string) *template.Template { return template.Must(template.New("").ParseFS(templatesFS, "templates/include/*.html", "templates/"+name)) } -func iconExtensions(config *config.Config) (template.JS, error) { +func iconExtensions(conf *config.Config) (template.JS, error) { extensionToURL := make(map[string]string) for _, ext := range assets.MimeExtensions() { - url, err := assets.AssetURL(config, "img/mime/small/"+ext+".png") + url, err := assets.AssetURL(conf, "img/mime/small/"+ext+".png") if err != nil { return "", fmt.Errorf("failed to get asset URL for %q: %w", ext, err) } @@ -50,8 +50,8 @@ func iconExtensions(config *config.Config) (template.JS, error) { return template.JS(json), nil } -func handleIndex(config *config.Config, logger logging.Logger) (http.HandlerFunc, error) { - extensions, err := iconExtensions(config) +func handleIndex(conf *config.Config, logger logging.Logger) (http.HandlerFunc, error) { + extensions, err := iconExtensions(conf) if err != nil { return nil, fmt.Errorf("iconExtensions: %w", err) } @@ -70,7 +70,7 @@ func handleIndex(config *config.Config, logger logging.Logger) (http.HandlerFunc IconExtensions template.JS Text string }{ - Meta: NewMeta(r.Context(), config, pageConfig{ + Meta: NewMeta(r.Context(), conf, pageConfig{ ID: "index", ExtraHTMLClasses: extraHTMLClasses, }), @@ -91,8 +91,8 @@ func handleIndex(config *config.Config, logger logging.Logger) (http.HandlerFunc }, nil } -func handleUploadHistory(config *config.Config, logger logging.Logger) (http.HandlerFunc, error) { - extensions, err := iconExtensions(config) +func handleUploadHistory(conf *config.Config, logger logging.Logger) (http.HandlerFunc, error) { + extensions, err := iconExtensions(conf) if err != nil { return nil, fmt.Errorf("iconExtensions: %w", err) } @@ -103,7 +103,7 @@ func handleUploadHistory(config *config.Config, logger logging.Logger) (http.Han Meta meta IconExtensions template.JS }{ - Meta: NewMeta(r.Context(), config, pageConfig{ + Meta: NewMeta(r.Context(), conf, pageConfig{ ID: "upload-history", }), IconExtensions: extensions, @@ -126,7 +126,7 @@ type UploadResponse struct { Error string `json:"error"` } -func handleUpload(config *config.Config, logger logging.Logger) http.HandlerFunc { +func handleUpload(conf *config.Config, logger logging.Logger) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { jsonError := func(statusCode int, msg string) { w.WriteHeader(statusCode) @@ -137,7 +137,7 @@ func handleUpload(config *config.Config, logger logging.Logger) http.HandlerFunc }) } - err := r.ParseMultipartForm(config.MaxMultipartMemoryBytes) + err := r.ParseMultipartForm(conf.MaxMultipartMemoryBytes) if err != nil { logger.Error(r.Context(), "parsing multipart form", "error", err) jsonError(http.StatusBadRequest, "Could not parse multipart form.") @@ -166,7 +166,7 @@ func handleUpload(config *config.Config, logger logging.Logger) http.HandlerFunc // TODO: check file size (keep in mind fileHeader.Size might be a lie?) // -- but maybe not? since Go buffers it first? - key, err := uploads.SanitizeUploadName(fileHeader.Filename, config.ForbiddenFileExtensions) + key, err := uploads.SanitizeUploadName(fileHeader.Filename, conf.ForbiddenFileExtensions) if err != nil { if errors.Is(err, uploads.ErrForbiddenExtension) { logger.Info(r.Context(), "forbidden extension", "filename", fileHeader.Filename) @@ -197,7 +197,7 @@ func handleUpload(config *config.Config, logger logging.Logger) http.HandlerFunc return } - errs := uploads.UploadObjects(r.Context(), logger, config, objs) + errs := uploads.UploadObjects(r.Context(), logger, conf, objs) if len(errs) > 0 { logger.Error(r.Context(), "uploading objects failed", "errors", errs)