mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 17:47:03 +00:00
Commit incomplete ParseEmailAddress()
This commit is contained in:
@@ -91,26 +91,35 @@ func ValidateDomainPart(domain string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateLocalPart returns true if the string complies with RFC3696 recommendations
|
// ParseEmailAddress unescapes an email address, and splits the local part from the domain part.
|
||||||
func ValidateLocalPart(local string) bool {
|
// An error is returned if the local or domain parts fail validation following the guidelines
|
||||||
length := len(local)
|
// in RFC3696.
|
||||||
if 1 > length || length > 64 {
|
func ParseEmailAddress(address string) (local string, domain string, err error) {
|
||||||
// Invalid length
|
if address == "" {
|
||||||
return false
|
return "", "", fmt.Errorf("Empty address")
|
||||||
}
|
}
|
||||||
if local[length-1] == '.' {
|
if len(address) > 320 {
|
||||||
// Cannot end with a period
|
return "", "", fmt.Errorf("Address exceeds 320 characters")
|
||||||
return false
|
}
|
||||||
|
if address[0] == '@' {
|
||||||
|
return "", "", fmt.Errorf("Address cannot start with @ symbol")
|
||||||
|
}
|
||||||
|
if address[0] == '.' {
|
||||||
|
return "", "", fmt.Errorf("Address cannot start with a period")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loop over address parsing out local part
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
prev := byte('.')
|
prev := byte('.')
|
||||||
inCharQuote := false
|
inCharQuote := false
|
||||||
inStringQuote := false
|
inStringQuote := false
|
||||||
for i := 0; i < length; i++ {
|
LOOP:
|
||||||
c := local[i]
|
for i := 0; i < len(address); i++ {
|
||||||
|
c := address[i]
|
||||||
switch {
|
switch {
|
||||||
case ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'):
|
case ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'):
|
||||||
// Letters are OK
|
// Letters are OK
|
||||||
|
buf.WriteByte(c)
|
||||||
inCharQuote = false
|
inCharQuote = false
|
||||||
case '0' <= c && c <= '9':
|
case '0' <= c && c <= '9':
|
||||||
// Numbers are OK
|
// Numbers are OK
|
||||||
@@ -122,31 +131,57 @@ func ValidateLocalPart(local string) bool {
|
|||||||
// A single period is OK
|
// A single period is OK
|
||||||
if prev == '.' {
|
if prev == '.' {
|
||||||
// Sequence of periods is not permitted
|
// Sequence of periods is not permitted
|
||||||
return false
|
return "", "", fmt.Errorf("Sequence of periods is not permitted")
|
||||||
}
|
}
|
||||||
case c == '\\':
|
case c == '\\':
|
||||||
inCharQuote = true
|
inCharQuote = true
|
||||||
case c == '"':
|
case c == '"':
|
||||||
if inCharQuote {
|
if inCharQuote {
|
||||||
inCharQuote = false
|
inCharQuote = false
|
||||||
|
} else if inStringQuote {
|
||||||
|
inStringQuote = false
|
||||||
} else {
|
} else {
|
||||||
inStringQuote = !inStringQuote
|
if i == 0 {
|
||||||
|
inStringQuote = true
|
||||||
|
} else {
|
||||||
|
return "", "", fmt.Errorf("Quoted string can only begin at start of address")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case c == '@':
|
||||||
|
if inCharQuote || inStringQuote {
|
||||||
|
inCharQuote = false
|
||||||
|
} else {
|
||||||
|
// End of local-part
|
||||||
|
if i > 63 {
|
||||||
|
return "", "", fmt.Errorf("Local part must not exceed 64 characters")
|
||||||
|
}
|
||||||
|
if prev == '.' {
|
||||||
|
return "", "", fmt.Errorf("Local part cannot end with a period")
|
||||||
|
}
|
||||||
|
domain = address[i+1:]
|
||||||
|
break LOOP
|
||||||
}
|
}
|
||||||
case c > 127:
|
case c > 127:
|
||||||
return false
|
return "", "", fmt.Errorf("Characters outside of US-ASCII range not permitted")
|
||||||
default:
|
default:
|
||||||
if inCharQuote || inStringQuote {
|
if inCharQuote || inStringQuote {
|
||||||
inCharQuote = false
|
inCharQuote = false
|
||||||
return true
|
} else {
|
||||||
|
return "", "", fmt.Errorf("Character %q must be quoted", c)
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
prev = c
|
prev = c
|
||||||
}
|
}
|
||||||
if inCharQuote || inStringQuote {
|
if inCharQuote {
|
||||||
// Can't end with unused backslash quote or unterminated string quote
|
return "", "", fmt.Errorf("Cannot end address with unterminated quoted-pair")
|
||||||
return false
|
}
|
||||||
|
if inStringQuote {
|
||||||
|
return "", "", fmt.Errorf("Cannot end address with unterminated string quote")
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
if !ValidateDomainPart(domain) {
|
||||||
|
return "", "", fmt.Errorf("Domain part validation failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), domain, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,8 +99,36 @@ func TestValidateLocal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range testTable {
|
for _, tt := range testTable {
|
||||||
if ValidateLocalPart(tt.input) != tt.expect {
|
_, _, err := ParseEmailAddress(tt.input + "@domain.com")
|
||||||
|
if (err != nil) == tt.expect {
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Got error: %s", err)
|
||||||
|
}
|
||||||
t.Errorf("Expected %v for %q: %s", tt.expect, tt.input, tt.msg)
|
t.Errorf("Expected %v for %q: %s", tt.expect, tt.input, tt.msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseEmailAddress(t *testing.T) {
|
||||||
|
var testTable = []struct {
|
||||||
|
input, local, domain string
|
||||||
|
}{
|
||||||
|
{"root@localhost", "root", "localhost"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testTable {
|
||||||
|
local, domain, err := ParseEmailAddress(tt.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error when parsing %q: %s", tt.input, err)
|
||||||
|
} else {
|
||||||
|
if tt.local != local {
|
||||||
|
t.Errorf("When parsing %q, expected local %q, got %q instead",
|
||||||
|
tt.input, tt.local, local)
|
||||||
|
}
|
||||||
|
if tt.domain != domain {
|
||||||
|
t.Errorf("When parsing %q, expected domain %q, got %q instead",
|
||||||
|
tt.input, tt.domain, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user