Files
libredesk/internal/stringutil/stringutil.go
Abhinav Raut 8551b65a27 fix: set references header in all outgoing emails, set the last 20 messages.
feat: set conversation reference number in the subject of conversation for better thread matching.
fix: hide CSAT link from conversation last message.
2025-03-05 03:49:22 +05:30

163 lines
3.9 KiB
Go

// Package stringutil provides string utility functions.
package stringutil
import (
"crypto/rand"
"encoding/base64"
"fmt"
"net/mail"
"net/url"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/k3a/html2text"
)
const (
PasswordDummy = "•"
)
var (
regexpNonAlNum = regexp.MustCompile(`[^a-zA-Z0-9\-_\.]+`)
regexpSpaces = regexp.MustCompile(`[\s]+`)
)
// HTML2Text converts HTML to text.
func HTML2Text(html string) string {
return strings.TrimSpace(html2text.HTML2Text(html))
}
// SanitizeFilename sanitizes the provided filename.
func SanitizeFilename(fName string) string {
// Trim whitespace.
name := strings.TrimSpace(fName)
// Replace whitespace and "/" with "-"
name = regexpSpaces.ReplaceAllString(name, "-")
// Remove or replace any non-alphanumeric characters
name = regexpNonAlNum.ReplaceAllString(name, "")
// Convert to lowercase
name = strings.ToLower(name)
return filepath.Base(name)
}
// RandomAlphanumeric generates a random alphanumeric string of length n.
func RandomAlphanumeric(n int) (string, error) {
const dictionary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
bytes := make([]byte, n)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
for k, v := range bytes {
bytes[k] = dictionary[v%byte(len(dictionary))]
}
return string(bytes), nil
}
// RandomNumeric generates a random numeric string of length n.
func RandomNumeric(n int) (string, error) {
const dictionary = "0123456789"
bytes := make([]byte, n)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
for k, v := range bytes {
bytes[k] = dictionary[v%byte(len(dictionary))]
}
return string(bytes), nil
}
// GetPathFromURL extracts the path from a URL.
func GetPathFromURL(u string) (string, error) {
parsedURL, err := url.Parse(u)
if err != nil {
return "", err
}
return parsedURL.Path, nil
}
// RemoveEmpty removes empty strings from a slice of strings.
func RemoveEmpty(s []string) []string {
var r []string
for _, str := range s {
if str != "" {
r = append(r, str)
}
}
return r
}
// GenerateEmailMessageID generates a RFC-compliant Message-ID for an email, does not include the angle brackets.
// The client is expected to wrap the returned string in angle brackets.
func GenerateEmailMessageID(messageID string, fromAddress string) (string, error) {
if messageID == "" {
return "", fmt.Errorf("messageID cannot be empty")
}
// Parse from address
addr, err := mail.ParseAddress(fromAddress)
if err != nil {
return "", fmt.Errorf("invalid from address: %w", err)
}
// Extract domain with validation
parts := strings.Split(addr.Address, "@")
if len(parts) != 2 || parts[1] == "" {
return "", fmt.Errorf("invalid domain in from address")
}
domain := parts[1]
// Generate cryptographic random component
random := make([]byte, 8)
if _, err := rand.Read(random); err != nil {
return "", fmt.Errorf("failed to generate random bytes: %w", err)
}
// Sanitize messageID for email Message-ID
cleaner := regexp.MustCompile(`[^\w.-]`) // Allow only alphanum, ., -, _
cleanmessageID := cleaner.ReplaceAllString(messageID, "_")
// Ensure cleaned messageID isn't empty
if cleanmessageID == "" {
return "", fmt.Errorf("messageID became empty after sanitization")
}
// Build RFC-compliant Message-ID
return fmt.Sprintf("%s-%d-%s@%s",
cleanmessageID,
time.Now().UnixNano(), // Nanosecond precision
strings.TrimRight(base64.URLEncoding.EncodeToString(random), "="), // URL-safe base64 without padding
domain,
), nil
}
// ReverseSlice reverses a slice of strings in place.
func ReverseSlice(source []string) {
for i, j := 0, len(source)-1; i < j; i, j = i+1, j-1 {
source[i], source[j] = source[j], source[i]
}
}
// RemoveItemByValue removes all instances of a value from a slice of strings.
func RemoveItemByValue(slice []string, value string) []string {
result := []string{}
for _, v := range slice {
if v != value {
result = append(result, v)
}
}
return result
}