add more message validation
This commit is contained in:
@@ -7,10 +7,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"landing/internal/config"
|
"landing/internal/config"
|
||||||
"landing/internal/database"
|
"landing/internal/database"
|
||||||
@@ -362,6 +364,180 @@ func (h *Handler) contactHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isEnglishText checks if text is primarily in English
|
||||||
|
func isEnglishText(text string) bool {
|
||||||
|
if len(text) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
englishCharCount := 0
|
||||||
|
nonASCIICount := 0
|
||||||
|
totalCharCount := 0
|
||||||
|
|
||||||
|
for _, r := range text {
|
||||||
|
// Count letters and numbers
|
||||||
|
if unicode.IsLetter(r) || unicode.IsNumber(r) || unicode.IsSpace(r) || unicode.IsPunct(r) {
|
||||||
|
totalCharCount++
|
||||||
|
|
||||||
|
// English ASCII range (a-z, A-Z, 0-9, common punctuation/spaces)
|
||||||
|
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') ||
|
||||||
|
r == ' ' || r == '.' || r == ',' || r == '!' || r == '?' || r == '-' || r == '\'' || r == '"' ||
|
||||||
|
r == ';' || r == ':' || r == '(' || r == ')' || r == '\n' || r == '\t' {
|
||||||
|
englishCharCount++
|
||||||
|
} else if r > 127 { // Non-ASCII character
|
||||||
|
nonASCIICount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalCharCount == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow up to 10% non-ASCII characters (for names, etc)
|
||||||
|
// But require at least 70% English ASCII
|
||||||
|
englishPercentage := float64(englishCharCount) / float64(totalCharCount)
|
||||||
|
|
||||||
|
return englishPercentage >= 0.7
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSpamMessage checks if a message looks like spam
|
||||||
|
func isSpamMessage(message string) bool {
|
||||||
|
// Convert to lowercase for checks
|
||||||
|
lowerMsg := strings.ToLower(message)
|
||||||
|
|
||||||
|
// Check for common spam patterns
|
||||||
|
spamPatterns := []string{
|
||||||
|
"viagra", "cialis", "casino", "lottery", "prize",
|
||||||
|
"click here", "buy now", "limited time",
|
||||||
|
"congratulations", "you have won", "claim your",
|
||||||
|
"bitcoin", "crypto", "forex", "trading bot",
|
||||||
|
"free money", "make money fast", "work from home",
|
||||||
|
"nigerian", "inheritance", "transfer funds",
|
||||||
|
"<!--", "javascript:", "onclick=", "<script",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pattern := range spamPatterns {
|
||||||
|
if strings.Contains(lowerMsg, pattern) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for excessive URLs
|
||||||
|
urlRegex := regexp.MustCompile(`https?://`)
|
||||||
|
if len(urlRegex.FindAllString(lowerMsg, -1)) > 2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for excessive special characters (spam often has many !)
|
||||||
|
exclamationCount := strings.Count(lowerMsg, "!")
|
||||||
|
if exclamationCount > 3 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for repeated characters (spam often has "!!!", "...", etc)
|
||||||
|
if strings.Contains(lowerMsg, "!!!") || strings.Contains(lowerMsg, "???") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for all caps (usually spam)
|
||||||
|
if len(lowerMsg) > 20 {
|
||||||
|
letterCount := 0
|
||||||
|
capsCount := 0
|
||||||
|
for _, r := range message {
|
||||||
|
if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') {
|
||||||
|
letterCount++
|
||||||
|
if r >= 'A' && r <= 'Z' {
|
||||||
|
capsCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if letterCount > 0 && capsCount/letterCount > 0.7 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for repeated words
|
||||||
|
words := strings.Fields(lowerMsg)
|
||||||
|
if len(words) > 5 {
|
||||||
|
wordCount := make(map[string]int)
|
||||||
|
for _, word := range words {
|
||||||
|
wordCount[word]++
|
||||||
|
}
|
||||||
|
for _, count := range wordCount {
|
||||||
|
if count > 4 { // Same word appears more than 4 times
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidName checks if name looks legitimate
|
||||||
|
func isValidName(name string) bool {
|
||||||
|
// Name should be at least 2 characters and at most 100
|
||||||
|
if len(name) < 2 || len(name) > 100 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name should not contain excessive numbers
|
||||||
|
numberCount := 0
|
||||||
|
for _, r := range name {
|
||||||
|
if r >= '0' && r <= '9' {
|
||||||
|
numberCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if numberCount > len(name)/3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name should not contain URLs
|
||||||
|
if strings.Contains(name, "http") || strings.Contains(name, "://") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidEmail checks if email looks legitimate
|
||||||
|
func isValidEmail(email string) bool {
|
||||||
|
// Basic email validation
|
||||||
|
if !strings.Contains(email, "@") || !strings.Contains(email, ".") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(email, "@")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local part should be 1-64 chars
|
||||||
|
if len(parts[0]) < 1 || len(parts[0]) > 64 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain part should be 3-255 chars
|
||||||
|
if len(parts[1]) < 3 || len(parts[1]) > 255 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for valid domain structure
|
||||||
|
domainParts := strings.Split(parts[1], ".")
|
||||||
|
if len(domainParts) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each domain label should be 1-63 chars
|
||||||
|
for _, label := range domainParts {
|
||||||
|
if len(label) < 1 || len(label) > 63 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) handleContactSubmission(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) handleContactSubmission(w http.ResponseWriter, r *http.Request) {
|
||||||
// Parse form data
|
// Parse form data
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
@@ -390,17 +566,47 @@ func (h *Handler) handleContactSubmission(w http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate email format (basic validation)
|
// Validate name format
|
||||||
if !strings.Contains(email, "@") || !strings.Contains(email, ".") {
|
if !isValidName(name) {
|
||||||
|
h.logger.Printf("⚠ Rejected submission: Invalid name format - %s", name)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
json.NewEncoder(w).Encode(map[string]string{
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
"error": "Invalid email address",
|
"error": "Please provide a valid name",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate message length (prevent spam)
|
// Validate email format
|
||||||
|
if !isValidEmail(email) {
|
||||||
|
h.logger.Printf("⚠ Rejected submission: Invalid email format - %s", email)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": "Please provide a valid email address",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate subject
|
||||||
|
validSubjects := map[string]bool{
|
||||||
|
"general": true,
|
||||||
|
"support": true,
|
||||||
|
"partnership": true,
|
||||||
|
"feedback": true,
|
||||||
|
"other": true,
|
||||||
|
}
|
||||||
|
if !validSubjects[subject] {
|
||||||
|
h.logger.Printf("⚠ Rejected submission: Invalid subject - %s", subject)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": "Please select a valid subject",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate message length
|
||||||
if len(message) < 10 {
|
if len(message) < 10 {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
@@ -419,6 +625,28 @@ func (h *Handler) handleContactSubmission(w http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if message is in English
|
||||||
|
if !isEnglishText(message) {
|
||||||
|
h.logger.Printf("⚠ Rejected submission: Non-English message from %s (%s)", name, email)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": "Please submit your message in English",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if message is spam
|
||||||
|
if isSpamMessage(message) {
|
||||||
|
h.logger.Printf("⚠ Rejected spam submission from %s (%s): %s", name, email, message[:100])
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": "Your message was flagged as spam. Please try again with a different message.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If subscribe checkbox is checked, add to subscribers
|
// If subscribe checkbox is checked, add to subscribers
|
||||||
if subscribe {
|
if subscribe {
|
||||||
if err := h.db.AddSubscriber(r.Context(), email); err != nil {
|
if err := h.db.AddSubscriber(r.Context(), email); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user