Repository: jhillyerd/enmime
Pull request #233 (2022-02-13 19:19:15)

envelope: Move ensureCommaDelimitedAddresses
This function doesn't belong in envelope, and it is confusing that
its unit test is in an unrelated file.



Patch:
diff --git a/envelope.go b/envelope.go
index b34c888..bd49676 100644
--- a/envelope.go
+++ b/envelope.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/jaytaylor/html2text"
 	"github.com/jhillyerd/enmime/internal/coding"
+	"github.com/jhillyerd/enmime/internal/stringutil"
 	"github.com/jhillyerd/enmime/mediatype"
 	"github.com/pkg/errors"
 )
@@ -120,7 +121,7 @@ func (e *Envelope) AddressList(key string) ([]*mail.Address, error) {
 	if err != nil {
 		switch err.Error() {
 		case "mail: expected comma":
-			return mail.ParseAddressList(ensureCommaDelimitedAddresses(str))
+			return mail.ParseAddressList(stringutil.EnsureCommaDelimitedAddresses(str))
 		case "mail: no address":
 			return nil, mail.ErrHeaderNotPresent
 		}
@@ -337,62 +338,3 @@ func parseMultiPartBody(root *Part, e *Envelope) error {
 func matchHTMLBodyPart(p *Part) bool {
 	return p.ContentType == ctTextHTML && p.Disposition != cdAttachment
 }
-
-// Used by AddressList to ensure that address lists are properly delimited
-func ensureCommaDelimitedAddresses(s string) string {
-	// This normalizes the whitespace, but may interfere with CFWS (comments with folding whitespace)
-	// RFC-5322 3.4.0:
-	//      because some legacy implementations interpret the comment,
-	//      comments generally SHOULD NOT be used in address fields
-	//      to avoid confusing such implementations.
-	s = strings.Join(strings.Fields(s), " ")
-
-	inQuotes := false
-	inDomain := false
-	escapeSequence := false
-	sb := strings.Builder{}
-	for _, r := range s {
-		if escapeSequence {
-			escapeSequence = false
-			sb.WriteRune(r)
-			continue
-		}
-		if r == '"' {
-			inQuotes = !inQuotes
-			sb.WriteRune(r)
-			continue
-		}
-		if inQuotes {
-			if r == '\\' {
-				escapeSequence = true
-				sb.WriteRune(r)
-				continue
-			}
-		} else {
-			if r == '@' {
-				inDomain = true
-				sb.WriteRune(r)
-				continue
-			}
-			if inDomain {
-				if r == ';' {
-					sb.WriteRune(r)
-					break
-				}
-				if r == ',' {
-					inDomain = false
-					sb.WriteRune(r)
-					continue
-				}
-				if r == ' ' {
-					inDomain = false
-					sb.WriteRune(',')
-					sb.WriteRune(r)
-					continue
-				}
-			}
-		}
-		sb.WriteRune(r)
-	}
-	return sb.String()
-}
diff --git a/internal/stringutil/addr.go b/internal/stringutil/addr.go
index 2121492..660aba6 100644
--- a/internal/stringutil/addr.go
+++ b/internal/stringutil/addr.go
@@ -3,6 +3,7 @@ package stringutil
 import (
 	"bytes"
 	"net/mail"
+	"strings"
 )
 
 // JoinAddress formats a slice of Address structs such that they can be used in a To or Cc header.
@@ -19,3 +20,63 @@ func JoinAddress(addrs []mail.Address) string {
 	}
 	return buf.String()
 }
+
+// EnsureCommaDelimitedAddresses is used by AddressList to ensure that address lists are properly
+// delimited.
+func EnsureCommaDelimitedAddresses(s string) string {
+	// This normalizes the whitespace, but may interfere with CFWS (comments with folding whitespace)
+	// RFC-5322 3.4.0:
+	//      because some legacy implementations interpret the comment,
+	//      comments generally SHOULD NOT be used in address fields
+	//      to avoid confusing such implementations.
+	s = strings.Join(strings.Fields(s), " ")
+
+	inQuotes := false
+	inDomain := false
+	escapeSequence := false
+	sb := strings.Builder{}
+	for _, r := range s {
+		if escapeSequence {
+			escapeSequence = false
+			sb.WriteRune(r)
+			continue
+		}
+		if r == '"' {
+			inQuotes = !inQuotes
+			sb.WriteRune(r)
+			continue
+		}
+		if inQuotes {
+			if r == '\\' {
+				escapeSequence = true
+				sb.WriteRune(r)
+				continue
+			}
+		} else {
+			if r == '@' {
+				inDomain = true
+				sb.WriteRune(r)
+				continue
+			}
+			if inDomain {
+				if r == ';' {
+					sb.WriteRune(r)
+					break
+				}
+				if r == ',' {
+					inDomain = false
+					sb.WriteRune(r)
+					continue
+				}
+				if r == ' ' {
+					inDomain = false
+					sb.WriteRune(',')
+					sb.WriteRune(r)
+					continue
+				}
+			}
+		}
+		sb.WriteRune(r)
+	}
+	return sb.String()
+}


Test patch:
diff --git a/header_test.go b/header_test.go
index 01055be..9238f6c 100644
--- a/header_test.go
+++ b/header_test.go
@@ -271,65 +271,3 @@ func TestReadHeader(t *testing.T) {
 		})
 	}
 }
-
-func TestCommaDelimitedAddressLists(t *testing.T) {
-	testData := []struct {
-		have string
-		want string
-	}{
-		{
-			have: `"Joe @ Company" <joe@company.com> <other@company.com>`,
-			want: `"Joe @ Company" <joe@company.com>, <other@company.com>`,
-		},
-		{
-			have: `Joe Company <joe@company.com> <other@company.com>`,
-			want: `Joe Company <joe@company.com>, <other@company.com>`,
-		},
-		{
-			have: `Joe Company:Joey <joe@company.com> John <other@company.com>;`,
-			want: `Joe Company:Joey <joe@company.com>, John <other@company.com>;`,
-		},
-		{
-			have: `Joe Company:Joey <joe@company.com> John <other@company.com>; Jimmy John <jimmy.john@company.com>`,
-			want: `Joe Company:Joey <joe@company.com>, John <other@company.com>;`,
-		},
-		{
-			have: `Joe Company <joe@company.com> John Company <other@company.com>`,
-			want: `Joe Company <joe@company.com>, John Company <other@company.com>`,
-		},
-		{
-			have: `Joe Company <joe@company.com>,John Company <other@company.com>`,
-			want: `Joe Company <joe@company.com>,John Company <other@company.com>`,
-		},
-		{
-			have: `joe@company.com other@company.com`,
-			want: `joe@company.com, other@company.com`,
-		},
-		{
-			have: `Jimmy John <jimmy.john@company.com> joe@company.com other@company.com`,
-			want: `Jimmy John <jimmy.john@company.com>, joe@company.com, other@company.com`,
-		},
-		{
-			have: `Jimmy John <jimmy.john@company.com> joe@company.com John Company <other@company.com>`,
-			want: `Jimmy John <jimmy.john@company.com>, joe@company.com, John Company <other@company.com>`,
-		},
-		{
-			have: `<boss@nil.test> "Giant; \"Big\" Box" <sysservices@example.net>`,
-			want: `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`,
-		},
-		{
-			have: `A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;`,
-			want: `A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;`,
-		},
-		{
-			have: `A Group:Ed Jones <c@a.test> joe@where.test John <jdoe@one.test>;`,
-			want: `A Group:Ed Jones <c@a.test>, joe@where.test, John <jdoe@one.test>;`,
-		},
-	}
-	for i := range testData {
-		v := ensureCommaDelimitedAddresses(testData[i].have)
-		if testData[i].want != v {
-			t.Fatalf("Expected %s, but got %s", testData[i].want, v)
-		}
-	}
-}
diff --git a/internal/stringutil/addr_test.go b/internal/stringutil/addr_test.go
index 7f6d3e3..96d84f6 100644
--- a/internal/stringutil/addr_test.go
+++ b/internal/stringutil/addr_test.go
@@ -46,3 +46,65 @@ func TestJoinAddressMany(t *testing.T) {
 		t.Errorf("got: %q, want: %q", got, want)
 	}
 }
+
+func TestCommaDelimitedAddressLists(t *testing.T) {
+	testData := []struct {
+		have string
+		want string
+	}{
+		{
+			have: `"Joe @ Company" <joe@company.com> <other@company.com>`,
+			want: `"Joe @ Company" <joe@company.com>, <other@company.com>`,
+		},
+		{
+			have: `Joe Company <joe@company.com> <other@company.com>`,
+			want: `Joe Company <joe@company.com>, <other@company.com>`,
+		},
+		{
+			have: `Joe Company:Joey <joe@company.com> John <other@company.com>;`,
+			want: `Joe Company:Joey <joe@company.com>, John <other@company.com>;`,
+		},
+		{
+			have: `Joe Company:Joey <joe@company.com> John <other@company.com>; Jimmy John <jimmy.john@company.com>`,
+			want: `Joe Company:Joey <joe@company.com>, John <other@company.com>;`,
+		},
+		{
+			have: `Joe Company <joe@company.com> John Company <other@company.com>`,
+			want: `Joe Company <joe@company.com>, John Company <other@company.com>`,
+		},
+		{
+			have: `Joe Company <joe@company.com>,John Company <other@company.com>`,
+			want: `Joe Company <joe@company.com>,John Company <other@company.com>`,
+		},
+		{
+			have: `joe@company.com other@company.com`,
+			want: `joe@company.com, other@company.com`,
+		},
+		{
+			have: `Jimmy John <jimmy.john@company.com> joe@company.com other@company.com`,
+			want: `Jimmy John <jimmy.john@company.com>, joe@company.com, other@company.com`,
+		},
+		{
+			have: `Jimmy John <jimmy.john@company.com> joe@company.com John Company <other@company.com>`,
+			want: `Jimmy John <jimmy.john@company.com>, joe@company.com, John Company <other@company.com>`,
+		},
+		{
+			have: `<boss@nil.test> "Giant; \"Big\" Box" <sysservices@example.net>`,
+			want: `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`,
+		},
+		{
+			have: `A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;`,
+			want: `A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;`,
+		},
+		{
+			have: `A Group:Ed Jones <c@a.test> joe@where.test John <jdoe@one.test>;`,
+			want: `A Group:Ed Jones <c@a.test>, joe@where.test, John <jdoe@one.test>;`,
+		},
+	}
+	for i := range testData {
+		v := stringutil.EnsureCommaDelimitedAddresses(testData[i].have)
+		if testData[i].want != v {
+			t.Fatalf("Expected %s, but got %s", testData[i].want, v)
+		}
+	}
+}