diff --git a/feature/header.go b/feature/header.go new file mode 100644 index 0000000000000000000000000000000000000000..1fb5a09789f22eb0b6ed2fbd0ae6719ca1011131 --- /dev/null +++ b/feature/header.go @@ -0,0 +1,48 @@ +package feature + +import ( + "context" + "net/http" + "strings" +) + +var featureCtxKey = &contextKey{"feature"} + +type contextKey struct { + name string +} + +// Creates an HTTP middleware to process Accept-Features headers. +func Middleware() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + header := r.Header.Get("Accept-Features") + features := strings.Split(header, ",") + + ctx := context.WithValue(r.Context(), + featureCtxKey, features) + r = r.WithContext(ctx) + next.ServeHTTP(w, r) + }) + } +} + +// Returns the list of enabled features on this context. +func ForContext(ctx context.Context) []string { + raw, ok := ctx.Value(featureCtxKey).([]string) + if !ok { + return []string{} + } + return raw +} + +// Returns true if the requested feature is enabled via the Accept-Features +// HTTP header +func Enabled(ctx context.Context, name string) bool { + for _, feat := range ForContext(ctx) { + if feat == name { + return true + } + } + return false +} diff --git a/server/server.go b/server/server.go index f92d70b92abcfdd820c59977ee06ef5b2c04baa2..18987318ab71e4527c85e0281bfa5f69ac8cd2ac 100644 --- a/server/server.go +++ b/server/server.go @@ -39,6 +39,7 @@ "git.sr.ht/~sircmpwn/core-go/config" "git.sr.ht/~sircmpwn/core-go/crypto" "git.sr.ht/~sircmpwn/core-go/database" "git.sr.ht/~sircmpwn/core-go/email" + "git.sr.ht/~sircmpwn/core-go/feature" "git.sr.ht/~sircmpwn/core-go/redis" ) @@ -254,7 +255,15 @@ }) server.router.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{"*"}, AllowedMethods: []string{"GET", "POST", "OPTIONS"}, - AllowedHeaders: []string{"User-Agent", "X-Requested-With", "If-Modified-Since", "Cache-Control", "Content-Type", "Range"}, + AllowedHeaders: []string{ + "User-Agent", + "X-Requested-With", + "If-Modified-Since", + "Cache-Control", + "Content-Type", + "Range", + "Accept-Features", + }, ExposedHeaders: []string{"Content-Length", "Content-Range"}, MaxAge: 1728000, })) @@ -264,6 +273,7 @@ server.router.Use(database.Middleware(db)) server.router.Use(redis.Middleware(rc)) server.router.Use(auth.Middleware(server.conf, apiconf)) server.router.Use(middleware.RealIP) + server.router.Use(feature.Middleware()) if debug { server.router.Use(middleware.Logger) }