// kafka — a privacy-respecting metasearch engine // Copyright (C) 2026-present metamorphosis-dev // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package middleware import ( "net/http" "strconv" "strings" ) // CORSConfig controls Cross-Origin Resource Sharing headers. type CORSConfig struct { // AllowedOrigins is a list of allowed origin patterns. // Use "*" to allow all origins, or specific domains like "https://example.com". AllowedOrigins []string // AllowedMethods defaults to GET, POST, OPTIONS if empty. AllowedMethods []string // AllowedHeaders defaults to Content-Type, Authorization if empty. AllowedHeaders []string // ExposedHeaders lists headers the browser can access from the response. ExposedHeaders []string // MaxAge is the preflight cache duration in seconds (default: 3600). MaxAge int } // CORS returns a middleware that sets CORS headers on all responses // and handles OPTIONS preflight requests. func CORS(cfg CORSConfig) func(http.Handler) http.Handler { origins := cfg.AllowedOrigins if len(origins) == 0 { origins = []string{"*"} } methods := cfg.AllowedMethods if len(methods) == 0 { methods = []string{"GET", "POST", "OPTIONS"} } headers := cfg.AllowedHeaders if len(headers) == 0 { headers = []string{"Content-Type", "Authorization", "X-Search-Token", "X-Brave-Access-Token"} } maxAge := cfg.MaxAge if maxAge <= 0 { maxAge = 3600 } methodsStr := strings.Join(methods, ", ") headersStr := strings.Join(headers, ", ") exposedStr := strings.Join(cfg.ExposedHeaders, ", ") maxAgeStr := strconv.Itoa(maxAge) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") // Determine the allowed origin for this request. allowedOrigin := "" for _, o := range origins { if o == "*" { allowedOrigin = "*" break } if o == origin { allowedOrigin = origin break } } if allowedOrigin != "" { w.Header().Set("Access-Control-Allow-Origin", allowedOrigin) w.Header().Set("Access-Control-Allow-Methods", methodsStr) w.Header().Set("Access-Control-Allow-Headers", headersStr) if exposedStr != "" { w.Header().Set("Access-Control-Expose-Headers", exposedStr) } w.Header().Set("Access-Control-Max-Age", maxAgeStr) } // Handle preflight. if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } }