1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 17:47:03 +00:00
Files
go-inbucket/smtpd/utils.go
2013-11-03 20:53:37 -08:00

145 lines
3.0 KiB
Go

package smtpd
import (
"bytes"
"container/list"
"crypto/sha1"
"fmt"
"io"
"strings"
)
// Take "user+ext@host.com" and return "user", aka the mailbox we'll store it in
func ParseMailboxName(emailAddress string) (result string) {
result = strings.ToLower(emailAddress)
if idx := strings.Index(result, "@"); idx > -1 {
result = result[0:idx]
}
if idx := strings.Index(result, "+"); idx > -1 {
result = result[0:idx]
}
return result
}
// Take a mailbox name and hash it into the directory we'll store it in
func HashMailboxName(mailbox string) string {
h := sha1.New()
io.WriteString(h, mailbox)
return fmt.Sprintf("%x", h.Sum(nil))
}
// JoinStringList joins a List containing strings by commas
func JoinStringList(listOfStrings *list.List) string {
if listOfStrings.Len() == 0 {
return ""
}
s := make([]string, 0, listOfStrings.Len())
for e := listOfStrings.Front(); e != nil; e = e.Next() {
s = append(s, e.Value.(string))
}
return strings.Join(s, ",")
}
// ValidateDomainPart returns true if the domain part complies to RFC3696, RFC1035
func ValidateDomainPart(domain string) bool {
if len(domain) == 0 {
return false
}
if len(domain) > 255 {
return false
}
if domain[len(domain)-1] != '.' {
domain += "."
}
prev := '.'
labelLen := 0
hasLetters := false
for _, c := range domain {
switch {
case ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_':
// Must contain some of these to be a valid label
hasLetters = true
labelLen++
case '0' <= c && c <= '9':
labelLen++
case c == '-':
if prev == '.' {
// Cannot lead with hyphen
return false
}
case c == '.':
if prev == '.' || prev == '-' {
// Cannot end with hyphen or double-dot
return false
}
if labelLen > 63 {
return false
}
if !hasLetters {
return false
}
labelLen = 0
hasLetters = false
default:
// Unknown character
return false
}
prev = c
}
return true
}
// ValidateLocalPart returns true if the string complies with RFC3696 recommendations
func ValidateLocalPart(local string) bool {
length := len(local)
if 1 > length || length > 64 {
// Invalid length
return false
}
if local[length-1] == '.' {
// Cannot end with a period
return false
}
prev := byte('.')
inCharQuote := false
for i := 0; i < length; i++ {
c := local[i]
switch {
case ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'):
// Letters are OK
inCharQuote = false
case '0' <= c && c <= '9':
// Numbers are OK
inCharQuote = false
case bytes.IndexByte([]byte("!#$%&'*+-/=?^_`{|}~"), c) >= 0:
// These specials can be used unquoted
inCharQuote = false
case c == '.':
// A single period is OK
if prev == '.' {
// Sequence of periods is not permitted
return false
}
case c == '\\':
inCharQuote = true
case c > 127:
return false
default:
if ! inCharQuote {
return false
}
inCharQuote = false
}
prev = c
}
if inCharQuote {
// Can't end with unused backslash quote
return false
}
return true
}