header analyzer fix large headers

This commit is contained in:
nahakubuilde
2025-07-18 07:33:11 +01:00
parent 597ddef66f
commit 20cfcd1829
13 changed files with 8166 additions and 165 deletions

7550
example.txt Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -3,12 +3,19 @@ package parser
import ( import (
"embed" "embed"
"html/template" "html/template"
"log"
"net/http" "net/http"
"net/url"
"strings" "strings"
"time"
"headeranalyzer/security"
) )
type Handler struct { type Handler struct {
templates *template.Template templates *template.Template
csrf *security.CSRFManager
validator *security.InputValidator
} }
func NewHandler(embeddedFiles embed.FS) *Handler { func NewHandler(embeddedFiles embed.FS) *Handler {
@@ -56,13 +63,38 @@ func NewHandler(embeddedFiles embed.FS) *Handler {
return &Handler{ return &Handler{
templates: tmpl, templates: tmpl,
csrf: security.NewCSRFManager(time.Hour),
validator: security.NewInputValidator(),
} }
} }
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost { if r.Method == http.MethodPost {
// Validate CSRF token
csrfToken := r.FormValue("csrf_token")
if !h.csrf.ValidateToken(csrfToken) {
http.Redirect(w, r, "/?error="+url.QueryEscape("Invalid security token. Please try again."), http.StatusSeeOther)
return
}
// Get and validate headers input
headers := r.FormValue("headers") headers := r.FormValue("headers")
report := Analyze(headers) log.Printf("DEBUG: Received headers input: %d characters", len(headers))
validatedHeaders, err := h.validator.ValidateEmailHeaders(headers)
if err != nil {
log.Printf("DEBUG: Validation failed: %v", err)
if security.IsValidationError(err) {
http.Redirect(w, r, "/?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
return
}
http.Redirect(w, r, "/?error="+url.QueryEscape("Invalid input provided"), http.StatusSeeOther)
return
}
log.Printf("DEBUG: Headers validated successfully")
report := Analyze(validatedHeaders)
log.Printf("DEBUG: Analysis completed, From field: '%s'", report.From)
// Create a wrapper struct to include current page info // Create a wrapper struct to include current page info
data := struct { data := struct {
*Report *Report
@@ -71,16 +103,34 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Report: report, Report: report,
CurrentPage: "home", CurrentPage: "home",
} }
h.templates.ExecuteTemplate(w, "headeranalyzer.html", data)
log.Printf("DEBUG: About to render template with data")
err = h.templates.ExecuteTemplate(w, "headeranalyzer.html", data)
if err != nil {
log.Printf("ERROR: Template execution failed: %v", err)
http.Error(w, "Template rendering failed", http.StatusInternalServerError)
return
}
log.Printf("DEBUG: Template rendered successfully")
return return
} }
// Generate CSRF token for GET requests
csrfToken, err := h.csrf.GenerateToken()
if err != nil {
http.Redirect(w, r, "/?error="+url.QueryEscape("Security token generation failed"), http.StatusSeeOther)
return
}
// For GET requests, create an empty report so template conditions work // For GET requests, create an empty report so template conditions work
data := struct { data := struct {
*Report *Report
CurrentPage string CurrentPage string
CSRFToken string
}{ }{
Report: &Report{}, // Empty report so .From will be empty string Report: &Report{}, // Empty report so .From will be empty string
CurrentPage: "home", CurrentPage: "home",
CSRFToken: csrfToken,
} }
h.templates.ExecuteTemplate(w, "headeranalyzer.html", data) h.templates.ExecuteTemplate(w, "headeranalyzer.html", data)
} }

View File

@@ -3,8 +3,12 @@ package passwordgenerator
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"headeranalyzer/security"
) )
var validator = security.NewInputValidator()
// PasswordAPIHandler handles password generation requests // PasswordAPIHandler handles password generation requests
func PasswordAPIHandler(w http.ResponseWriter, r *http.Request) { func PasswordAPIHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
@@ -32,6 +36,45 @@ func PasswordAPIHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// Validate input parameters
if requestData.Length < 4 || requestData.Length > 128 {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Length must be between 4 and 128"))
return
}
if requestData.NumberCount < 0 || requestData.NumberCount > 20 {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Number count must be between 0 and 20"))
return
}
if requestData.WordCount < 2 || requestData.WordCount > 10 {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Word count must be between 2 and 10"))
return
}
if len(requestData.SpecialChars) > 50 {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Special characters string too long"))
return
}
// Validate type parameter
if requestData.Type != "random" && requestData.Type != "passphrase" {
requestData.Type = "passphrase" // Default to passphrase
}
// Validate number position
validPositions := map[string]bool{"start": true, "end": true, "each": true}
if !validPositions[requestData.NumberPosition] {
requestData.NumberPosition = "end" // Default
}
// Sanitize special characters to prevent potential issues
requestData.SpecialChars = validator.SanitizeHTML(requestData.SpecialChars)
// Convert to internal Config format // Convert to internal Config format
config := Config{ config := Config{
Length: requestData.Length, Length: requestData.Length,

View File

@@ -4,12 +4,18 @@ import (
"embed" "embed"
"html/template" "html/template"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"strings" "strings"
"time"
"headeranalyzer/security"
) )
type Handler struct { type Handler struct {
templates *template.Template templates *template.Template
csrf *security.CSRFManager
validator *security.InputValidator
} }
type PasswordConfig struct { type PasswordConfig struct {
@@ -57,10 +63,19 @@ func NewHandler(embeddedFiles embed.FS) *Handler {
return &Handler{ return &Handler{
templates: tmpl, templates: tmpl,
csrf: security.NewCSRFManager(time.Hour),
validator: security.NewInputValidator(),
} }
} }
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Generate CSRF token
csrfToken, err := h.csrf.GenerateToken()
if err != nil {
http.Redirect(w, r, "/password?error="+url.QueryEscape("Security token generation failed"), http.StatusSeeOther)
return
}
// Parse URL parameters to set default values // Parse URL parameters to set default values
config := PasswordConfig{ config := PasswordConfig{
Type: getStringParam(r, "type", "passphrase"), Type: getStringParam(r, "type", "passphrase"),
@@ -80,9 +95,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
data := struct { data := struct {
CurrentPage string CurrentPage string
Config PasswordConfig Config PasswordConfig
CSRFToken string
}{ }{
CurrentPage: "password", CurrentPage: "password",
Config: config, Config: config,
CSRFToken: csrfToken,
} }
h.templates.ExecuteTemplate(w, "password.html", data) h.templates.ExecuteTemplate(w, "password.html", data)
} }

View File

@@ -9,7 +9,6 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"html"
"html/template" "html/template"
"io" "io"
"io/fs" "io/fs"
@@ -17,11 +16,11 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode/utf8"
"headeranalyzer/security"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@@ -33,7 +32,8 @@ type PWPusher struct {
pushTemplates *template.Template pushTemplates *template.Template
viewTemplates *template.Template viewTemplates *template.Template
failedAttempts map[string]*FailedAttempts failedAttempts map[string]*FailedAttempts
csrfTokens map[string]time.Time // Map of CSRF tokens to their creation time csrf *security.CSRFManager
validator *security.InputValidator
} }
type FailedAttempts struct { type FailedAttempts struct {
@@ -115,12 +115,6 @@ const (
CSRFTokenExpiry = 1 * time.Hour // CSRF tokens expire after 1 hour CSRFTokenExpiry = 1 * time.Hour // CSRF tokens expire after 1 hour
) )
// Input validation patterns
var (
allowedActionPattern = regexp.MustCompile(`^(reveal|verify_password|delete)$`)
safeStringPattern = regexp.MustCompile(`^[\p{L}\p{N}\p{P}\p{Z}\p{S}\s]*$`)
)
// NewPWPusher creates a new PWPusher instance // NewPWPusher creates a new PWPusher instance
func NewPWPusher(embeddedFS fs.FS, encryptionKey string) (*PWPusher, error) { func NewPWPusher(embeddedFS fs.FS, encryptionKey string) (*PWPusher, error) {
if encryptionKey == "" { if encryptionKey == "" {
@@ -156,7 +150,8 @@ func NewPWPusher(embeddedFS fs.FS, encryptionKey string) (*PWPusher, error) {
pushTemplates: pushTemplates, pushTemplates: pushTemplates,
viewTemplates: viewTemplates, viewTemplates: viewTemplates,
failedAttempts: make(map[string]*FailedAttempts), failedAttempts: make(map[string]*FailedAttempts),
csrfTokens: make(map[string]time.Time), csrf: security.NewCSRFManager(CSRFTokenExpiry),
validator: security.NewInputValidator(),
}, nil }, nil
} }
@@ -470,29 +465,7 @@ func (p *PWPusher) getBlockedUntil(clientIP string) time.Time {
return time.Time{} return time.Time{}
} }
func (p *PWPusher) getClientIP(r *http.Request) string { // HTTP Handlers
// Check X-Forwarded-For header for real IP behind proxy
forwarded := r.Header.Get("X-Forwarded-For")
if forwarded != "" {
// Get the first IP in the list
ips := strings.Split(forwarded, ",")
clientIP := strings.TrimSpace(ips[0])
log.Printf("Using X-Forwarded-For IP: %s", clientIP)
return clientIP
}
// Check X-Real-IP header
realIP := r.Header.Get("X-Real-IP")
if realIP != "" {
log.Printf("Using X-Real-IP: %s", realIP)
return realIP
}
// Fall back to remote address
clientIP := strings.Split(r.RemoteAddr, ":")[0]
log.Printf("Using RemoteAddr IP: %s", clientIP)
return clientIP
} // HTTP Handlers
func (p *PWPusher) IndexHandler(w http.ResponseWriter, r *http.Request) { func (p *PWPusher) IndexHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("PWPusher IndexHandler called: %s %s", r.Method, r.URL.Path) log.Printf("PWPusher IndexHandler called: %s %s", r.Method, r.URL.Path)
@@ -574,24 +547,24 @@ func (p *PWPusher) handleCreatePush(w http.ResponseWriter, r *http.Request) {
} }
// Comprehensive input validation // Comprehensive input validation
sanitizedText, err := p.validateAndSanitizeText(req.Text) sanitizedText, err := p.validator.ValidateAndSanitizeText(req.Text, 100000)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
req.Text = sanitizedText req.Text = sanitizedText
if err := p.validatePassword(req.Password); err != nil { if err := p.validator.ValidatePassword(req.Password); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
if err := p.validateInteger(req.ExpiryDays, MinExpiryDays, MaxExpiryDays, "expiry days"); err != nil { if err := p.validator.ValidateIntRange(req.ExpiryDays, MinExpiryDays, MaxExpiryDays, "expiry days"); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
if err := p.validateInteger(req.MaxViews, MinViews, MaxViews, "max views"); err != nil { if err := p.validator.ValidateIntRange(req.MaxViews, MinViews, MaxViews, "max views"); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
@@ -611,7 +584,7 @@ func (p *PWPusher) handleCreatePush(w http.ResponseWriter, r *http.Request) {
id := p.generateID() id := p.generateID()
now := time.Now() now := time.Now()
expiresAt := now.AddDate(0, 0, req.ExpiryDays) expiresAt := now.AddDate(0, 0, req.ExpiryDays)
creatorIP := p.getClientIP(r) creatorIP := p.validator.GetClientIP(r)
// Hash password if provided // Hash password if provided
var passwordHash sql.NullString var passwordHash sql.NullString
@@ -794,7 +767,7 @@ func (p *PWPusher) ViewHandler(w http.ResponseWriter, r *http.Request) {
return return
} else if action == "verify_password" { } else if action == "verify_password" {
// Handle password verification with rate limiting // Handle password verification with rate limiting
clientIP := p.getClientIP(r) clientIP := p.validator.GetClientIP(r)
log.Printf("Password verification attempt from IP: %s", clientIP) log.Printf("Password verification attempt from IP: %s", clientIP)
// Check if client is blocked // Check if client is blocked
@@ -937,7 +910,7 @@ func (p *PWPusher) ViewHandler(w http.ResponseWriter, r *http.Request) {
// Check if password is required and not yet verified // Check if password is required and not yet verified
if push.PasswordHash != "" { if push.PasswordHash != "" {
clientIP := p.getClientIP(r) clientIP := p.validator.GetClientIP(r)
// Check if client is blocked // Check if client is blocked
if p.isBlocked(clientIP) { if p.isBlocked(clientIP) {
@@ -1089,112 +1062,13 @@ func (p *PWPusher) StatusHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
// Input validation and sanitization functions
func (p *PWPusher) validateAndSanitizeText(text string) (string, error) {
if text == "" {
return "", fmt.Errorf("text cannot be empty")
}
if len(text) > MaxTextLength {
return "", fmt.Errorf("text too long (max %d characters)", MaxTextLength)
}
if !utf8.ValidString(text) {
return "", fmt.Errorf("text contains invalid UTF-8 characters")
}
// Store the original text without HTML escaping
// The template will handle safe display
return text, nil
}
func (p *PWPusher) validatePassword(password string) error {
if len(password) > MaxPasswordLength {
return fmt.Errorf("password too long (max %d characters)", MaxPasswordLength)
}
if len(password) > 0 && len(password) < MinPasswordLength {
return fmt.Errorf("password too short (min %d characters)", MinPasswordLength)
}
if !utf8.ValidString(password) {
return fmt.Errorf("password contains invalid UTF-8 characters")
}
return nil
}
func (p *PWPusher) validateInteger(value, min, max int, fieldName string) error {
if value < min || value > max {
return fmt.Errorf("%s must be between %d and %d", fieldName, min, max)
}
return nil
}
func (p *PWPusher) validateAction(action string) error {
if !allowedActionPattern.MatchString(action) {
return fmt.Errorf("invalid action")
}
return nil
}
func (p *PWPusher) sanitizeString(input string) string {
// Remove any potentially dangerous characters and HTML escape
sanitized := html.EscapeString(strings.TrimSpace(input))
// Limit length
if len(sanitized) > 1000 {
sanitized = sanitized[:1000]
}
return sanitized
}
// CSRF token management // CSRF token management
func (p *PWPusher) generateCSRFToken() (string, error) { func (p *PWPusher) generateCSRFToken() (string, error) {
bytes := make([]byte, 32) return p.csrf.GenerateToken()
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
token := base64.URLEncoding.EncodeToString(bytes)
p.csrfTokens[token] = time.Now()
// Clean up expired tokens
p.cleanupExpiredCSRFTokens()
return token, nil
} }
func (p *PWPusher) validateCSRFToken(token string) bool { func (p *PWPusher) validateCSRFToken(token string) bool {
if token == "" { return p.csrf.ValidateToken(token)
return false
}
createdAt, exists := p.csrfTokens[token]
if !exists {
return false
}
// Check if token has expired
if time.Since(createdAt) > CSRFTokenExpiry {
delete(p.csrfTokens, token)
return false
}
// Remove token after use (one-time use)
delete(p.csrfTokens, token)
return true
}
func (p *PWPusher) cleanupExpiredCSRFTokens() {
now := time.Now()
for token, createdAt := range p.csrfTokens {
if now.Sub(createdAt) > CSRFTokenExpiry {
delete(p.csrfTokens, token)
}
}
} }
// Add CSRF token to ViewData // Add CSRF token to ViewData
@@ -1313,16 +1187,6 @@ func (p *PWPusher) setHistoryCookies(w http.ResponseWriter, history []map[string
}) })
} }
func (p *PWPusher) clearHistoryCookies(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: "pwpush_history",
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
})
}
func (p *PWPusher) renderTemplate(w http.ResponseWriter, templateName string, data interface{}) { func (p *PWPusher) renderTemplate(w http.ResponseWriter, templateName string, data interface{}) {
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")

View File

@@ -7,32 +7,68 @@ import (
"net/http" "net/http"
"strings" "strings"
"headeranalyzer/security"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
var validator = security.NewInputValidator()
// DNSAPIHandler handles DNS lookup requests // DNSAPIHandler handles DNS lookup requests
func DNSAPIHandler(w http.ResponseWriter, r *http.Request) { func DNSAPIHandler(w http.ResponseWriter, r *http.Request) {
// Validate and sanitize query parameter
query := r.URL.Query().Get("query") query := r.URL.Query().Get("query")
typeq := r.URL.Query().Get("type") validatedQuery, err := validator.ValidateDNSQuery(query)
if query == "" || typeq == "" { if err != nil {
w.WriteHeader(400) w.WriteHeader(400)
w.Write([]byte("Missing query or type")) w.Write([]byte("Invalid query: " + err.Error()))
return return
} }
// Validate type parameter
typeq := r.URL.Query().Get("type")
if typeq == "" {
w.WriteHeader(400)
w.Write([]byte("Missing type parameter"))
return
}
// Validate allowed DNS types
allowedTypes := map[string]bool{
"A": true, "AAAA": true, "MX": true, "TXT": true, "NS": true,
"CNAME": true, "PTR": true, "SOA": true, "SPF": true,
"DKIM": true, "DMARC": true, "WHOIS": true,
}
if !allowedTypes[typeq] {
w.WriteHeader(400)
w.Write([]byte("Invalid DNS type"))
return
}
// Validate and sanitize server parameter if provided
dnsServer := r.URL.Query().Get("server") dnsServer := r.URL.Query().Get("server")
if dnsServer != "" {
// Basic validation for DNS server format
if len(dnsServer) > 100 || strings.ContainsAny(dnsServer, "\r\n\x00") {
w.WriteHeader(400)
w.Write([]byte("Invalid DNS server"))
return
}
}
var result string var result string
switch typeq { switch typeq {
case "WHOIS": case "WHOIS":
result = handleWHOISQuery(query) result = handleWHOISQuery(validatedQuery)
case "SPF": case "SPF":
result = handleSPFQuery(query, dnsServer) result = handleSPFQuery(validatedQuery, dnsServer)
case "DMARC": case "DMARC":
result = handleDMARCQuery(query, dnsServer) result = handleDMARCQuery(validatedQuery, dnsServer)
case "DKIM": case "DKIM":
result = handleDKIMQuery(query, dnsServer, r) result = handleDKIMQuery(validatedQuery, dnsServer, r)
default: default:
result = handleStandardDNSQuery(query, typeq, dnsServer) result = handleStandardDNSQuery(validatedQuery, typeq, dnsServer)
} }
w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")

View File

@@ -4,11 +4,17 @@ import (
"embed" "embed"
"html/template" "html/template"
"net/http" "net/http"
"net/url"
"strings" "strings"
"time"
"headeranalyzer/security"
) )
type Handler struct { type Handler struct {
templates *template.Template templates *template.Template
csrf *security.CSRFManager
validator *security.InputValidator
} }
func NewHandler(embeddedFiles embed.FS) *Handler { func NewHandler(embeddedFiles embed.FS) *Handler {
@@ -41,14 +47,25 @@ func NewHandler(embeddedFiles embed.FS) *Handler {
return &Handler{ return &Handler{
templates: tmpl, templates: tmpl,
csrf: security.NewCSRFManager(time.Hour),
validator: security.NewInputValidator(),
} }
} }
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Generate CSRF token for the page
csrfToken, err := h.csrf.GenerateToken()
if err != nil {
http.Redirect(w, r, "/dns?error="+url.QueryEscape("Security token generation failed"), http.StatusSeeOther)
return
}
data := struct { data := struct {
CurrentPage string CurrentPage string
CSRFToken string
}{ }{
CurrentPage: "dns", CurrentPage: "dns",
CSRFToken: csrfToken,
} }
h.templates.ExecuteTemplate(w, "dns.html", data) h.templates.ExecuteTemplate(w, "dns.html", data)
} }

84
security/csrf.go Normal file
View File

@@ -0,0 +1,84 @@
package security
import (
"crypto/rand"
"encoding/base64"
"sync"
"time"
)
// CSRFManager handles CSRF token generation and validation
type CSRFManager struct {
tokens map[string]time.Time
mutex sync.RWMutex
expiry time.Duration
}
// NewCSRFManager creates a new CSRF manager with the specified token expiry duration
func NewCSRFManager(expiry time.Duration) *CSRFManager {
return &CSRFManager{
tokens: make(map[string]time.Time),
expiry: expiry,
}
}
// GenerateToken creates a new CSRF token
func (c *CSRFManager) GenerateToken() (string, error) {
bytes := make([]byte, 32)
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
token := base64.URLEncoding.EncodeToString(bytes)
c.mutex.Lock()
defer c.mutex.Unlock()
c.tokens[token] = time.Now()
c.cleanupExpiredTokensUnsafe()
return token, nil
}
// ValidateToken validates a CSRF token and removes it (one-time use)
func (c *CSRFManager) ValidateToken(token string) bool {
if token == "" {
return false
}
c.mutex.Lock()
defer c.mutex.Unlock()
createdAt, exists := c.tokens[token]
if !exists {
return false
}
// Check if token has expired
if time.Since(createdAt) > c.expiry {
delete(c.tokens, token)
return false
}
// Remove token after use (one-time use)
delete(c.tokens, token)
return true
}
// cleanupExpiredTokensUnsafe removes expired tokens (must be called with mutex locked)
func (c *CSRFManager) cleanupExpiredTokensUnsafe() {
now := time.Now()
for token, createdAt := range c.tokens {
if now.Sub(createdAt) > c.expiry {
delete(c.tokens, token)
}
}
}
// CleanupExpiredTokens removes expired tokens (thread-safe)
func (c *CSRFManager) CleanupExpiredTokens() {
c.mutex.Lock()
defer c.mutex.Unlock()
c.cleanupExpiredTokensUnsafe()
}

336
security/validation.go Normal file
View File

@@ -0,0 +1,336 @@
package security
import (
"fmt"
"html"
"net/http"
"regexp"
"strings"
"unicode/utf8"
)
// InputValidator provides input validation and sanitization methods
type InputValidator struct{}
// NewInputValidator creates a new input validator
func NewInputValidator() *InputValidator {
return &InputValidator{}
}
// SanitizeHTML escapes HTML characters to prevent XSS
func (v *InputValidator) SanitizeHTML(input string) string {
return html.EscapeString(input)
}
// ValidateAndSanitizeText validates text input and removes dangerous content
func (v *InputValidator) ValidateAndSanitizeText(text string, maxLength int) (string, error) {
// Check for empty input
if strings.TrimSpace(text) == "" {
return "", &ValidationError{Field: "text", Message: "Text cannot be empty"}
}
// Check length limits
if len(text) > maxLength {
return "", &ValidationError{Field: "text", Message: "Text exceeds maximum length"}
}
// Check for valid UTF-8
if !utf8.ValidString(text) {
return "", &ValidationError{Field: "text", Message: "Text contains invalid characters"}
}
// Remove null bytes and control characters (except normal whitespace)
cleaned := regexp.MustCompile(`[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]`).ReplaceAllString(text, "")
// Normalize line endings
cleaned = strings.ReplaceAll(cleaned, "\r\n", "\n")
cleaned = strings.ReplaceAll(cleaned, "\r", "\n")
return cleaned, nil
}
// ValidatePassword validates password requirements
func (v *InputValidator) ValidatePassword(password string) error {
if len(password) < 1 {
return &ValidationError{Field: "password", Message: "Password cannot be empty"}
}
if len(password) > 1000 {
return &ValidationError{Field: "password", Message: "Password too long"}
}
// Check for valid UTF-8
if !utf8.ValidString(password) {
return &ValidationError{Field: "password", Message: "Password contains invalid characters"}
}
return nil
}
// ValidateEmailHeaders validates email header input with enhanced detection and large file handling
func (v *InputValidator) ValidateEmailHeaders(headers string) (string, error) {
if strings.TrimSpace(headers) == "" {
return "", &ValidationError{Field: "headers", Message: "Email headers cannot be empty"}
}
// Check for valid UTF-8
if !utf8.ValidString(headers) {
return "", &ValidationError{Field: "headers", Message: "Headers contain invalid characters"}
}
// Extract only the header portion for large files
processedHeaders := v.extractEmailHeadersOnly(headers)
// Check if we have valid email headers
if !v.containsValidEmailHeaders(processedHeaders) {
return "", &ValidationError{Field: "headers", Message: "No valid email headers found. Please provide actual email headers."}
}
// After processing, check reasonable size limit
if len(processedHeaders) > 200*1024 { // 200KB after processing
return "", &ValidationError{Field: "headers", Message: "Email headers too large after processing"}
}
return processedHeaders, nil
}
// extractEmailHeadersOnly extracts only the email headers from potentially large files
func (v *InputValidator) extractEmailHeadersOnly(content string) string {
var result strings.Builder
lines := strings.Split(content, "\n")
headerSection := true
headerLines := 0
maxHeaderLines := 1000
for _, line := range lines {
line = strings.TrimRight(line, "\r")
// Stop if we've processed too many header lines (safety limit)
if headerLines >= maxHeaderLines {
break
}
// Empty line typically separates headers from body
if strings.TrimSpace(line) == "" {
if headerSection && headerLines > 0 {
// End of headers section
break
}
continue
}
// MIME boundary indicates end of headers/start of body content
if strings.HasPrefix(line, "--") && strings.Contains(line, "boundary") {
break
}
// Skip obvious encoded content (base64-like long strings without spaces)
if len(line) > 200 && !strings.Contains(line, " ") && !strings.Contains(line, ":") {
continue
}
// Skip lines that look like encoded content
if v.looksLikeEncodedContent(line) {
continue
}
// Check if this looks like a header line
if headerSection && (v.looksLikeEmailHeader(line) || v.isHeaderContinuation(line)) {
result.WriteString(line)
result.WriteString("\n")
headerLines++
} else if headerSection && !v.looksLikeEmailHeader(line) && !v.isHeaderContinuation(line) {
// If we encounter a non-header line in header section, we might be done with headers
if headerLines > 5 { // Only stop if we've seen some headers already
break
}
}
}
return result.String()
}
// containsValidEmailHeaders checks if the content contains actual email headers
func (v *InputValidator) containsValidEmailHeaders(content string) bool {
lines := strings.Split(content, "\n")
headerCount := 0
commonHeaders := []string{
"received:", "from:", "to:", "subject:", "date:", "message-id:",
"return-path:", "delivered-to:", "authentication-results:",
"dkim-signature:", "content-type:", "mime-version:", "x-",
"reply-to:", "cc:", "bcc:", "sender:", "list-id:",
}
for _, line := range lines {
line = strings.ToLower(strings.TrimSpace(line))
if line == "" {
continue
}
// Check if this line contains a common email header
for _, header := range commonHeaders {
if strings.HasPrefix(line, header) {
headerCount++
break
}
}
// Also check for basic header format
if strings.Contains(line, ":") && !strings.HasPrefix(line, " ") && !strings.HasPrefix(line, "\t") {
// Additional validation for header format
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
headerName := strings.TrimSpace(parts[0])
// Header name should contain only valid characters
if regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`).MatchString(headerName) {
headerCount++
}
}
}
}
// Need at least 3 valid headers to consider it email headers
return headerCount >= 3
}
// looksLikeEmailHeader checks if a line looks like an email header
func (v *InputValidator) looksLikeEmailHeader(line string) bool {
if strings.TrimSpace(line) == "" {
return false
}
// Header continuation lines start with space or tab
if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
return false // These are handled separately
}
// Must contain a colon
if !strings.Contains(line, ":") {
return false
}
// Split on first colon
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
return false
}
headerName := strings.TrimSpace(parts[0])
// Header name should not be empty and should contain only valid characters
if headerName == "" {
return false
}
// Valid header name pattern
validHeaderName := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
return validHeaderName.MatchString(headerName)
}
// isHeaderContinuation checks if a line is a header continuation
func (v *InputValidator) isHeaderContinuation(line string) bool {
return strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t")
}
// looksLikeEncodedContent checks if a line looks like encoded content that should be skipped
func (v *InputValidator) looksLikeEncodedContent(line string) bool {
line = strings.TrimSpace(line)
// Very long lines without spaces are likely encoded
if len(line) > 100 && !strings.Contains(line, " ") && !strings.Contains(line, ":") {
return true
}
// Base64-like patterns (long strings of alphanumeric + / + =)
if len(line) > 50 {
base64Pattern := regexp.MustCompile(`^[A-Za-z0-9+/=\s]+$`)
if base64Pattern.MatchString(line) && !strings.Contains(line, ":") {
// Count alphanumeric characters vs spaces
alphanumeric := regexp.MustCompile(`[A-Za-z0-9+/=]`).FindAllString(line, -1)
if float64(len(alphanumeric)) > float64(len(line))*0.8 { // More than 80% alphanumeric
return true
}
}
}
return false
}
// ValidateDNSQuery validates DNS query input
func (v *InputValidator) ValidateDNSQuery(query string) (string, error) {
query = strings.TrimSpace(query)
if query == "" {
return "", &ValidationError{Field: "query", Message: "DNS query cannot be empty"}
}
if len(query) > 253 { // Maximum domain name length
return "", &ValidationError{Field: "query", Message: "DNS query too long"}
}
// Allow only valid DNS characters: letters, numbers, dots, hyphens, and colons (for IPv6)
validDNS := regexp.MustCompile(`^[a-zA-Z0-9\.\-:]+$`)
if !validDNS.MatchString(query) {
return "", &ValidationError{Field: "query", Message: "DNS query contains invalid characters"}
}
return query, nil
}
// ValidateIntRange validates integer input within a range
func (v *InputValidator) ValidateIntRange(value, min, max int, fieldName string) error {
if value < min || value > max {
return &ValidationError{
Field: fieldName,
Message: fmt.Sprintf("Value must be between %d and %d", min, max),
}
}
return nil
}
// GetClientIP extracts the real client IP from request headers
func (v *InputValidator) GetClientIP(r *http.Request) string {
// Check X-Forwarded-For header first
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
// Take the first IP in the list
ips := strings.Split(xff, ",")
if len(ips) > 0 {
ip := strings.TrimSpace(ips[0])
if ip != "" {
return ip
}
}
}
// Check X-Real-IP header
if xri := r.Header.Get("X-Real-IP"); xri != "" {
return strings.TrimSpace(xri)
}
// Fall back to RemoteAddr
ip := r.RemoteAddr
if colonIndex := strings.LastIndex(ip, ":"); colonIndex != -1 {
ip = ip[:colonIndex]
}
// Remove IPv6 brackets
ip = strings.Trim(ip, "[]")
return ip
}
// ValidationError represents a validation error
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return e.Message
}
// IsValidationError checks if an error is a validation error
func IsValidationError(err error) bool {
_, ok := err.(*ValidationError)
return ok
}

View File

@@ -92,7 +92,7 @@ document.getElementById('dnsForm').addEventListener('submit', function() {
<p><small>Time: ${timestamp} | Server: ${server || 'Default'}</small></p> <p><small>Time: ${timestamp} | Server: ${server || 'Default'}</small></p>
<pre>${data}</pre> <pre>${data}</pre>
`; `;
document.getElementById('dnsResults').appendChild(resultDiv); document.getElementById('dnsResults').insertBefore(resultDiv, document.getElementById('dnsResults').firstChild);
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
@@ -102,7 +102,7 @@ document.getElementById('dnsForm').addEventListener('submit', function() {
<h3>Error querying ${query}</h3> <h3>Error querying ${query}</h3>
<pre>Error: ${error.message}</pre> <pre>Error: ${error.message}</pre>
`; `;
document.getElementById('dnsResults').appendChild(resultDiv); document.getElementById('dnsResults').insertBefore(resultDiv, document.getElementById('dnsResults').firstChild);
}); });
}); });

View File

@@ -13,7 +13,8 @@
<h1>Email Header Analyzer</h1> <h1>Email Header Analyzer</h1>
{{if not .From}} {{if not .From}}
<form method="POST"> <form method="POST">
<textarea name="headers" placeholder="Paste email headers here..."></textarea> <input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<textarea name="headers" placeholder="Paste email headers here..." required></textarea>
<br> <br>
<button type="submit">Analyze Headers</button> <button type="submit">Analyze Headers</button>
</form> </form>

View File

@@ -6,6 +6,9 @@
<div class="password-generator"> <div class="password-generator">
<h1>🔐 Password Generator</h1> <h1>🔐 Password Generator</h1>
<!-- Hidden CSRF token for API calls -->
<input type="hidden" id="csrfToken" value="{{.CSRFToken}}">
<div class="tab-buttons"> <div class="tab-buttons">
<button class="tab-btn" id="randomTab">Random Password</button> <button class="tab-btn" id="randomTab">Random Password</button>
<button class="tab-btn active" id="passphraseTab">Passphrase</button> <button class="tab-btn active" id="passphraseTab">Passphrase</button>