255 lines
6.7 KiB
Go
Executable file
255 lines
6.7 KiB
Go
Executable file
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type UserSettings struct {
|
|
Theme string
|
|
SiteLanguage string
|
|
SearchLanguage string
|
|
SafeSearch string
|
|
IsThemeDark bool
|
|
}
|
|
|
|
func loadUserSettings(w http.ResponseWriter, r *http.Request) UserSettings {
|
|
var settings UserSettings
|
|
saveRequired := false
|
|
|
|
for _, cd := range AllCookies {
|
|
// Attempt to read the cookie
|
|
if cookie, err := r.Cookie(cd.Name); err == nil {
|
|
// Use SetValue to update the correct UserSettings field
|
|
cd.SetValue(&settings, cookie.Value)
|
|
} else {
|
|
// If cookie is missing and you want a default value, set it here
|
|
switch cd.Name {
|
|
case "theme":
|
|
// Default theme to "dark" if missing
|
|
cd.SetValue(&settings, "dark")
|
|
saveRequired = true
|
|
case "site_language":
|
|
// Fallback to Accept-Language or "en"
|
|
acceptLang := r.Header.Get("Accept-Language")
|
|
if acceptLang != "" {
|
|
cd.SetValue(&settings, normalizeLangCode(acceptLang))
|
|
} else {
|
|
cd.SetValue(&settings, "en")
|
|
}
|
|
saveRequired = true
|
|
case "safe":
|
|
// Default safe to ""
|
|
cd.SetValue(&settings, "")
|
|
saveRequired = true
|
|
// etc. for other cookies if needed
|
|
}
|
|
}
|
|
}
|
|
|
|
// If theme was set, update IsThemeDark just to be sure
|
|
// Alternatively do it inside SetValue for "theme"
|
|
settings.IsThemeDark = settings.Theme == "dark" ||
|
|
settings.Theme == "night" ||
|
|
settings.Theme == "black" ||
|
|
settings.Theme == "latte"
|
|
|
|
// Save any new default cookies that might have been triggered
|
|
if saveRequired {
|
|
saveUserSettings(w, settings)
|
|
}
|
|
|
|
return settings
|
|
}
|
|
|
|
func saveUserSettings(w http.ResponseWriter, settings UserSettings) {
|
|
expiration := time.Now().Add(90 * 24 * time.Hour)
|
|
|
|
for _, cd := range AllCookies {
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: cd.Name,
|
|
Value: cd.GetValue(settings),
|
|
Path: "/",
|
|
Expires: expiration,
|
|
Secure: true, // Ensure HTTPS is required
|
|
HttpOnly: true,
|
|
SameSite: http.SameSiteStrictMode, // Restrict cross-site usage
|
|
})
|
|
}
|
|
|
|
printDebug("settings saved: %v", settings)
|
|
}
|
|
|
|
func handleSaveSettings(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == "POST" {
|
|
// Load current settings
|
|
settings := loadUserSettings(w, r)
|
|
|
|
// Update only the settings that were submitted in the form
|
|
if theme := r.FormValue("theme"); theme != "" {
|
|
settings.Theme = theme
|
|
}
|
|
|
|
// Update site language if provided
|
|
if siteLang := r.FormValue("site_lang"); siteLang != "" {
|
|
settings.SiteLanguage = siteLang
|
|
} else {
|
|
// If site_lang is empty, try to get from Accept-Language header
|
|
acceptLang := r.Header.Get("Accept-Language")
|
|
if acceptLang != "" {
|
|
settings.SiteLanguage = strings.Split(acceptLang, ",")[0]
|
|
}
|
|
}
|
|
|
|
// Update search language if provided
|
|
if searchLang := r.FormValue("search_lang"); searchLang != "" {
|
|
settings.SearchLanguage = searchLang
|
|
}
|
|
|
|
// Update safe search if provided
|
|
if safe := r.FormValue("safe"); safe != "" {
|
|
settings.SafeSearch = safe
|
|
}
|
|
|
|
// Save the updated settings
|
|
saveUserSettings(w, settings)
|
|
|
|
// Redirect back to the previous page or settings page
|
|
http.Redirect(w, r, r.FormValue("past"), http.StatusSeeOther)
|
|
}
|
|
}
|
|
|
|
func handleSettings(w http.ResponseWriter, r *http.Request) {
|
|
// Load user settings
|
|
settings := loadUserSettings(w, r)
|
|
|
|
// Prepare data with user settings and icon paths as a map
|
|
data := map[string]interface{}{
|
|
"LanguageOptions": languageOptions,
|
|
"CurrentSiteLang": settings.SiteLanguage,
|
|
"CurrentSearchLang": settings.SearchLanguage,
|
|
"Theme": settings.Theme,
|
|
"Safe": settings.SafeSearch,
|
|
"IsThemeDark": settings.IsThemeDark,
|
|
}
|
|
|
|
printDebug("Rendering settings with data: %+v", data)
|
|
|
|
// Use renderTemplate to include the icons
|
|
renderTemplate(w, "settings.html", data)
|
|
}
|
|
|
|
// Helper function to normalize language codes
|
|
func normalizeLangCode(lang string) string {
|
|
lang = strings.ToLower(lang)
|
|
|
|
// First, check if the language code is already valid
|
|
if isValidLangCode(lang) {
|
|
return lang
|
|
}
|
|
|
|
// Strip regional codes (e.g., en-US -> en)
|
|
if strings.Contains(lang, "-") {
|
|
lang = strings.Split(lang, "-")[0]
|
|
}
|
|
|
|
// Re-check if the normalized version is valid
|
|
if isValidLangCode(lang) {
|
|
return lang
|
|
}
|
|
|
|
// If the language is not recognized, default to "en"
|
|
return "en"
|
|
}
|
|
|
|
// Helper function to check if a language code exists in the language options
|
|
func isValidLangCode(lang string) bool {
|
|
for _, opt := range languageOptions {
|
|
if opt.Code == lang {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// CookieDefinition describes how a single cookie is handled
|
|
type CookieDefinition struct {
|
|
Name string
|
|
// GetValue extracts the corresponding field from UserSettings
|
|
GetValue func(UserSettings) string
|
|
// SetValue updates the corresponding field in UserSettings
|
|
SetValue func(*UserSettings, string)
|
|
// Description used in privacy table or docs
|
|
Description string
|
|
}
|
|
|
|
// AllCookies defines every cookie we handle in a single slice.
|
|
// Add or remove entries here, and the rest updates automatically.
|
|
var AllCookies = []CookieDefinition{
|
|
{
|
|
Name: "theme",
|
|
Description: "Stores the selected theme (dark, light, etc.)",
|
|
GetValue: func(s UserSettings) string {
|
|
return s.Theme
|
|
},
|
|
SetValue: func(s *UserSettings, val string) {
|
|
s.Theme = val
|
|
s.IsThemeDark = (val == "dark" || val == "night" || val == "black" || val == "latte")
|
|
},
|
|
},
|
|
{
|
|
Name: "site_language",
|
|
Description: "Stores the preferred site language.",
|
|
GetValue: func(s UserSettings) string {
|
|
return s.SiteLanguage
|
|
},
|
|
SetValue: func(s *UserSettings, val string) {
|
|
s.SiteLanguage = val
|
|
},
|
|
},
|
|
{
|
|
Name: "search_language",
|
|
Description: "Stores the preferred language for search results.",
|
|
GetValue: func(s UserSettings) string {
|
|
return s.SearchLanguage
|
|
},
|
|
SetValue: func(s *UserSettings, val string) {
|
|
s.SearchLanguage = val
|
|
},
|
|
},
|
|
{
|
|
Name: "safe",
|
|
Description: "Stores the Safe Search setting.",
|
|
GetValue: func(s UserSettings) string {
|
|
return s.SafeSearch
|
|
},
|
|
SetValue: func(s *UserSettings, val string) {
|
|
s.SafeSearch = val
|
|
},
|
|
},
|
|
}
|
|
|
|
type CookieRow struct {
|
|
Name string
|
|
Value string
|
|
Description string
|
|
Expiration string
|
|
}
|
|
|
|
func generateCookieTable(r *http.Request) []CookieRow {
|
|
var rows []CookieRow
|
|
for _, cd := range AllCookies {
|
|
value := "[Not Set]"
|
|
if cookie, err := r.Cookie(cd.Name); err == nil {
|
|
value = cookie.Value
|
|
}
|
|
rows = append(rows, CookieRow{
|
|
Name: cd.Name,
|
|
Value: value,
|
|
Description: cd.Description,
|
|
Expiration: "90 days",
|
|
})
|
|
}
|
|
return rows
|
|
}
|