package main import ( "embed" "html/template" "io" "io/fs" "net/http" "os" "reflect" "regexp" "strings" "github.com/justinas/nosurf" ) //go:embed templates/*.html static/* var content embed.FS type TemplateData struct { CSRFToken string PageData interface{} Authenticated bool AppName string Notification string NotificationType string } func renderTemplate(w http.ResponseWriter, r *http.Request, tmpl string, pageData interface{}) { // Check if user is authenticated session, _ := store.Get(r, "session") isAuth := false if auth, ok := session.Values["authenticated"].(bool); ok { isAuth = auth } configMu.RLock() appName := config.AppName configMu.RUnlock() td := TemplateData{ CSRFToken: nosurf.Token(r), PageData: pageData, Authenticated: isAuth, AppName: appName, } // Extract notification and type from pageData if it has those fields if pageData != nil { v := reflect.ValueOf(pageData) if v.Kind() == reflect.Struct { notifField := v.FieldByName("Notification") typeField := v.FieldByName("NotificationType") if notifField.IsValid() && notifField.Kind() == reflect.String { td.Notification = notifField.String() } if typeField.IsValid() && typeField.Kind() == reflect.String { td.NotificationType = typeField.String() } } } templatesFS, _ := fs.Sub(content, "templates") // Parse base.html, notifications.html and the specific page template together files := []string{"base.html", "notifications.html", tmpl} t, err := template.ParseFS(templatesFS, files...) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Execute base.html which contains {{ block "content" . }} // The specific page template's {{ define "content" }} will override the block err = t.ExecuteTemplate(w, "base.html", td) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func copyFile(src, dst string) error { source, err := os.Open(src) if err != nil { return err } defer source.Close() destination, err := os.Create(dst) if err != nil { return err } defer destination.Close() _, err = io.Copy(destination, source) return err } func readLines(file string) ([]string, error) { data, err := os.ReadFile(file) if err != nil { return nil, err } lines := strings.Split(string(data), "\n") var cleaned []string for _, line := range lines { line = strings.TrimSpace(line) if line != "" { cleaned = append(cleaned, line) } } return cleaned, nil } func writeLines(file string, lines []string) { data := strings.Join(lines, "\n") os.WriteFile(file, []byte(data), 0644) } func mergeUnique(data1, data2 string) string { lines1 := strings.Split(data1, "\n") lines2 := strings.Split(data2, "\n") all := append(lines1, lines2...) // Sort for consistency var sorted []string seen := make(map[string]bool) for _, line := range all { line = strings.TrimSpace(line) if line == "" || seen[line] { continue } sorted = append(sorted, line) seen[line] = true } // Sort alphabetically for i := 0; i < len(sorted); i++ { for j := i + 1; j < len(sorted); j++ { if sorted[i] > sorted[j] { sorted[i], sorted[j] = sorted[j], sorted[i] } } } return strings.Join(sorted, "\n") } func sanitizeInput(input string) string { // Basic sanitization, remove dangerous chars re := regexp.MustCompile(`[;<>&|]`) return re.ReplaceAllString(input, "") } func isValidURL(u string) bool { return strings.HasPrefix(u, "http://") || strings.HasPrefix(u, "https://") } func isValidDomain(d string) bool { re := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9.-]*\.[a-zA-Z]{2,}$`) return re.MatchString(d) } func generateSecret() string { // This function is kept for backward compatibility but is not currently used // since we're using pquerna/otp for TOTP generation return "" }