215 lines
5.5 KiB
Go
215 lines
5.5 KiB
Go
package dkim
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
)
|
|
|
|
func TestStringToCanonicalization(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
want canonicalization
|
|
err error
|
|
}{
|
|
{"simple", simpleCanonicalization, nil},
|
|
{"relaxed", relaxedCanonicalization, nil},
|
|
{"", "", errUnknownCanonicalization},
|
|
{" ", "", errUnknownCanonicalization},
|
|
{" simple", "", errUnknownCanonicalization},
|
|
{"simple ", "", errUnknownCanonicalization},
|
|
{"si mple ", "", errUnknownCanonicalization},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
got, err := stringToCanonicalization(c.in)
|
|
if diff := cmp.Diff(c.want, got); diff != "" {
|
|
t.Errorf("stringToCanonicalization(%q) diff (-want +got): %s",
|
|
c.in, diff)
|
|
}
|
|
diff := cmp.Diff(c.err, err, cmpopts.EquateErrors())
|
|
if diff != "" {
|
|
t.Errorf("stringToCanonicalization(%q) err diff (-want +got): %s",
|
|
c.in, diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSimpleBody(t *testing.T) {
|
|
cases := []struct {
|
|
in, want string
|
|
}{
|
|
|
|
// Bodies end with \r\n, including the empty one.
|
|
{"", "\r\n"},
|
|
{"a", "a\r\n"},
|
|
{"a\r\n", "a\r\n"},
|
|
|
|
// Repeated CRLF at the end of the body is replaced with a single CRLF.
|
|
{"Body \r\n\r\n\r\n", "Body \r\n"},
|
|
|
|
// Example from RFC.
|
|
// https://datatracker.ietf.org/doc/html/rfc6376#section-3.4.5
|
|
{
|
|
" C \r\nD \t E\r\n\r\n\r\n",
|
|
" C \r\nD \t E\r\n",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
got := simpleCanonicalization.body(c.in)
|
|
if diff := cmp.Diff(c.want, got); diff != "" {
|
|
t.Errorf("simpleCanonicalization.body(%q) diff (-want +got): %s",
|
|
c.in, diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRelaxBody(t *testing.T) {
|
|
cases := []struct {
|
|
in, want string
|
|
}{
|
|
{"a\r\n", "a\r\n"},
|
|
|
|
// Repeated WSP before CRLF.
|
|
{"a \r\n", "a\r\n"},
|
|
{"a \r\n", "a\r\n"},
|
|
{"a \t \r\n", "a\r\n"},
|
|
{"a\t\t\t\r\n", "a\r\n"},
|
|
|
|
// Repeated WSP within a line.
|
|
{"a b\r\n", "a b\r\n"},
|
|
{"a\t\t\tb\r\n", "a b\r\n"},
|
|
{"a \t \t b\r\n", "a b\r\n"},
|
|
|
|
// Ignore empty lines at the end.
|
|
{"a\r\n\r\n", "a\r\n"},
|
|
{"a\r\n\r\n\r\n", "a\r\n"},
|
|
|
|
// Body must end with \r\n, unless it's empty.
|
|
{"", ""},
|
|
{"\r\n", "\r\n"},
|
|
{"a", "a\r\n"},
|
|
|
|
// Example from RFC.
|
|
// https://datatracker.ietf.org/doc/html/rfc6376#section-3.4.5
|
|
{" C \r\nD \t E\r\n\r\n\r\n", " C\r\nD E\r\n"},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
got := relaxedCanonicalization.body(c.in)
|
|
if diff := cmp.Diff(c.want, got); diff != "" {
|
|
t.Errorf("relaxedCanonicalization.body(%q) diff (-want +got): %s",
|
|
c.in, diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
func mkHs(hs ...string) headers {
|
|
var headers headers
|
|
for i := 0; i < len(hs); i += 2 {
|
|
h := header{
|
|
Name: hs[i],
|
|
Value: hs[i+1],
|
|
Source: hs[i] + ":" + hs[i+1],
|
|
}
|
|
headers = append(headers, h)
|
|
}
|
|
return headers
|
|
}
|
|
|
|
func TestHeaders(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
wantS headers
|
|
wantR headers
|
|
}{
|
|
// Unfold headers.
|
|
{"A: B\r\n C\r\n", mkHs("A", " B\r\n C"), mkHs("a", "B C")},
|
|
{"A: B\r\n\tC\r\n", mkHs("A", " B\r\n\tC"), mkHs("a", "B C")},
|
|
{"A: B\r\n \t C\r\n", mkHs("A", " B\r\n \t C"), mkHs("a", "B C")},
|
|
|
|
// Reduce all sequences of WSP within a line to a single SP.
|
|
{"A: B C\r\n", mkHs("A", " B C"), mkHs("a", "B C")},
|
|
{"A: B\t\tC\r\n", mkHs("A", " B\t\tC"), mkHs("a", "B C")},
|
|
{"A: B \t \t C\r\n", mkHs("A", " B \t \t C"), mkHs("a", "B C")},
|
|
|
|
// Delete all WSP at the end of each unfolded header field.
|
|
{"A: B \r\n", mkHs("A", " B "), mkHs("a", "B")},
|
|
{"A: B \r\n", mkHs("A", " B "), mkHs("a", "B")},
|
|
{"A: B\t \r\n", mkHs("A", " B\t "), mkHs("a", "B")},
|
|
{"A: B\t\t\t\r\n", mkHs("A", " B\t\t\t"), mkHs("a", "B")},
|
|
{"A: B\r\n \t C \t\r\n",
|
|
mkHs("A", " B\r\n \t C \t"), mkHs("a", "B C")},
|
|
|
|
// Whitespace before and after the colon.
|
|
{"A : B\r\n", mkHs("A ", " B"), mkHs("a", "B")},
|
|
{"A : B\r\n", mkHs("A ", " B"), mkHs("a", "B")},
|
|
{"A\t:\tB\r\n", mkHs("A\t", "\tB"), mkHs("a", "B")},
|
|
{"A\t \t : \t \tB\r\n", mkHs("A\t \t ", " \t \tB"), mkHs("a", "B")},
|
|
|
|
// Example from RFC.
|
|
// https://datatracker.ietf.org/doc/html/rfc6376#section-3.4.5
|
|
{"A: X\r\nB : Y\t\r\n\tZ \r\n",
|
|
mkHs("A", " X", "B ", " Y\t\r\n\tZ "),
|
|
mkHs("a", "X", "b", "Y Z")},
|
|
}
|
|
|
|
for i, c := range cases {
|
|
hs, _, err := parseMessage(c.in)
|
|
if err != nil {
|
|
t.Fatalf("parseMessage(%q) = %v, want nil", c.in, err)
|
|
}
|
|
|
|
gotS := simpleCanonicalization.headers(hs)
|
|
if diff := cmp.Diff(c.wantS, gotS); diff != "" {
|
|
t.Errorf("%d: simpleCanonicalization.headers(%q) diff (-want +got): %s",
|
|
i, c.in, diff)
|
|
}
|
|
|
|
gotR := relaxedCanonicalization.headers(hs)
|
|
if diff := cmp.Diff(c.wantR, gotR); diff != "" {
|
|
t.Errorf("%d: relaxedCanonicalization.headers(%q) diff (-want +got): %s",
|
|
i, c.in, diff)
|
|
}
|
|
|
|
// Test the single-header variant if possible.
|
|
if len(hs) == 1 {
|
|
gotS := simpleCanonicalization.header(hs[0])
|
|
if diff := cmp.Diff(c.wantS[0], gotS); diff != "" {
|
|
t.Errorf("%d: simpleCanonicalization.header(%q) diff (-want +got): %s",
|
|
i, c.in, diff)
|
|
}
|
|
|
|
gotR := relaxedCanonicalization.header(hs[0])
|
|
if diff := cmp.Diff(c.wantR[0], gotR); diff != "" {
|
|
t.Errorf("%d: relaxedCanonicalization.header(%q) diff (-want +got): %s",
|
|
i, c.in, diff)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBadCanonicalization(t *testing.T) {
|
|
bad := canonicalization("bad")
|
|
if !panics(func() { bad.body("") }) {
|
|
t.Errorf("bad.body() did not panic")
|
|
}
|
|
if !panics(func() { bad.header(header{}) }) {
|
|
t.Errorf("bad.header() did not panic")
|
|
}
|
|
if !panics(func() { bad.headers(nil) }) {
|
|
t.Errorf("bad.headers() did not panic")
|
|
}
|
|
}
|
|
|
|
func panics(f func()) (panicked bool) {
|
|
defer func() {
|
|
r := recover()
|
|
panicked = r != nil
|
|
}()
|
|
f()
|
|
return
|
|
}
|