mirror of
				https://github.com/abhinavxd/libredesk.git
				synced 2025-11-04 05:53:30 +00:00 
			
		
		
		
	- New permission `contacts:manage` - Views for contact list and single contact view. - Ability to block contacts that in inturn stops new messages from the contact. - Make all DB transactions that run dynamically generated SQL query `readonly` - Rename `/admin/teams/users` to `/admin/users/agents/` for consistency. (ref #50) - Fix UI glitches for long emails (refs #54) - Fix empty created_at date for agents admin table (refs #51) - Migrations for v0.6.0
		
			
				
	
	
		
			194 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			194 lines
		
	
	
		
			4.7 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
 | 
						|
}
 | 
						|
 | 
						|
// FormatDuration formats a duration as a string.
 | 
						|
func FormatDuration(d time.Duration, includeSeconds bool) string {
 | 
						|
	d = d.Round(time.Second)
 | 
						|
	h := int64(d.Hours())
 | 
						|
	d -= time.Duration(h) * time.Hour
 | 
						|
	m := int64(d.Minutes())
 | 
						|
	d -= time.Duration(m) * time.Minute
 | 
						|
	s := int64(d.Seconds())
 | 
						|
 | 
						|
	var parts []string
 | 
						|
	if h > 0 {
 | 
						|
		parts = append(parts, fmt.Sprintf("%d hours", h))
 | 
						|
	}
 | 
						|
	if m >= 0 {
 | 
						|
		parts = append(parts, fmt.Sprintf("%d minutes", m))
 | 
						|
	}
 | 
						|
	if s > 0 && includeSeconds {
 | 
						|
		parts = append(parts, fmt.Sprintf("%d seconds", s))
 | 
						|
	}
 | 
						|
	return strings.Join(parts, " ")
 | 
						|
}
 | 
						|
 | 
						|
// ValidEmail returns true if it's a valid email else return false.
 | 
						|
func ValidEmail(email string) bool {
 | 
						|
	addr, err := mail.ParseAddress(email)
 | 
						|
	if err != nil {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return addr.Name == "" && addr.Address == email
 | 
						|
}
 |