Home | History | Annotate | Download | only in email
      1 // Copyright 2017 syzkaller project authors. All rights reserved.
      2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
      3 
      4 package email
      5 
      6 import (
      7 	"fmt"
      8 	"reflect"
      9 	"strings"
     10 	"testing"
     11 
     12 	"github.com/google/go-cmp/cmp"
     13 )
     14 
     15 func TestExtractCommand(t *testing.T) {
     16 	for i, test := range extractCommandTests {
     17 		t.Run(fmt.Sprint(i), func(t *testing.T) {
     18 			cmd, args := extractCommand([]byte(test.body))
     19 			if cmd != test.cmd || !reflect.DeepEqual(args, test.args) {
     20 				t.Logf("expect: %q %q", test.cmd, test.args)
     21 				t.Logf("got   : %q %q", cmd, args)
     22 				t.Fail()
     23 			}
     24 			cmd, args = extractCommand([]byte(strings.Replace(test.body, "\n", "\r\n", -1)))
     25 			if cmd != test.cmd || !reflect.DeepEqual(args, test.args) {
     26 				t.Logf("expect: %q %q", test.cmd, test.args)
     27 				t.Logf("got   : %q %q", cmd, args)
     28 				t.Fail()
     29 			}
     30 		})
     31 	}
     32 }
     33 
     34 func TestAddRemoveAddrContext(t *testing.T) {
     35 	email := `"Foo Bar" <foo (a] bar.com>`
     36 	email00, context00, err := RemoveAddrContext(email)
     37 	if err != nil {
     38 		t.Fatal(err)
     39 	}
     40 	if email != email00 {
     41 		t.Fatalf("want: %q, got %q", email, email00)
     42 	}
     43 	if context00 != "" {
     44 		t.Fatalf("want context: %q, got %q", "", context00)
     45 	}
     46 	context1 := "context1"
     47 	email1, err := AddAddrContext(email, context1)
     48 	if err != nil {
     49 		t.Fatal(err)
     50 	}
     51 	want1 := `"Foo Bar" <foo+context1 (a] bar.com>`
     52 	if want1 != email1 {
     53 		t.Fatalf("want: %q, got %q", want1, email1)
     54 	}
     55 	context2 := "context2"
     56 	email2, err := AddAddrContext(email1, context2)
     57 	if err != nil {
     58 		t.Fatal(err)
     59 	}
     60 	want2 := `"Foo Bar" <foo+context1+context2 (a] bar.com>`
     61 	if want2 != email2 {
     62 		t.Fatalf("want: %q, got %q", want2, email2)
     63 	}
     64 	email1, context20, err := RemoveAddrContext(email2)
     65 	if err != nil {
     66 		t.Fatal(err)
     67 	}
     68 	if want1 != email1 {
     69 		t.Fatalf("want: %q, got %q", want1, email1)
     70 	}
     71 	if context2 != context20 {
     72 		t.Fatalf("want context: %q, got %q", context2, context20)
     73 	}
     74 	email0, context10, err := RemoveAddrContext(email1)
     75 	if err != nil {
     76 		t.Fatal(err)
     77 	}
     78 	if email != email0 {
     79 		t.Fatalf("want: %q, got %q", email, email0)
     80 	}
     81 	if context1 != context10 {
     82 		t.Fatalf("want context: %q, got %q", context1, context10)
     83 	}
     84 }
     85 
     86 func TestAddAddrContextEmptyName(t *testing.T) {
     87 	email := "<foo (a] bar.com>"
     88 	email1, err := AddAddrContext(email, "context")
     89 	if err != nil {
     90 		t.Fatal(err)
     91 	}
     92 	if want := "foo+context (a] bar.com"; want != email1 {
     93 		t.Fatalf("want: %q, got %q", want, email1)
     94 	}
     95 	email2, context1, err := RemoveAddrContext(email1)
     96 	if err != nil {
     97 		t.Fatal(err)
     98 	}
     99 	if email != email2 {
    100 		t.Fatalf("want: %q, got %q", email, email2)
    101 	}
    102 	if context1 != "context" {
    103 		t.Fatalf("got context %q", context1)
    104 	}
    105 }
    106 
    107 func TestCanonicalEmail(t *testing.T) {
    108 	canonical := "foo (a] bar.com"
    109 	emails := []string{
    110 		"\"Foo Bar\" <foo+123+456 (a] Bar.com>",
    111 		"<Foo (a] bar.com>",
    112 	}
    113 	for _, email := range emails {
    114 		if got := CanonicalEmail(email); got != canonical {
    115 			t.Errorf("got %q, want %q", got, canonical)
    116 		}
    117 	}
    118 }
    119 
    120 func TestParse(t *testing.T) {
    121 	for i, test := range parseTests {
    122 		body := func(t *testing.T, test ParseTest) {
    123 			email, err := Parse(strings.NewReader(test.email), []string{"bot <foo (a] bar.com>"})
    124 			if err != nil {
    125 				t.Fatal(err)
    126 			}
    127 			if diff := cmp.Diff(&test.res, email); diff != "" {
    128 				t.Error(diff)
    129 			}
    130 		}
    131 		t.Run(fmt.Sprint(i), func(t *testing.T) { body(t, test) })
    132 
    133 		test.email = strings.Replace(test.email, "\n", "\r\n", -1)
    134 		test.res.Body = strings.Replace(test.res.Body, "\n", "\r\n", -1)
    135 		t.Run(fmt.Sprint(i)+"rn", func(t *testing.T) { body(t, test) })
    136 	}
    137 }
    138 
    139 var extractCommandTests = []struct {
    140 	body string
    141 	cmd  string
    142 	args string
    143 }{
    144 	{
    145 		body: `Hello,
    146 
    147 line1
    148 #syz  fix:  bar baz 	`,
    149 		cmd:  "fix:",
    150 		args: "bar baz",
    151 	},
    152 	{
    153 		body: `Hello,
    154 
    155 line1
    156 #syz fix:  bar  	 baz
    157 line 2
    158 `,
    159 		cmd: "fix:",
    160 		args: "bar  	 baz",
    161 	},
    162 	{
    163 		body: `
    164 line1
    165 > #syz fix: bar   baz
    166 line 2
    167 `,
    168 		cmd:  "",
    169 		args: "",
    170 	},
    171 	// This is unfortunate case when a command is split by email client
    172 	// due to 80-column limitation.
    173 	{
    174 		body: `
    175 #syz test: git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git
    176 locking/core
    177 `,
    178 		cmd:  "test:",
    179 		args: "git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git locking/core",
    180 	},
    181 	{
    182 		body: `
    183 #syz test:
    184 git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git locking/core
    185 `,
    186 		cmd:  "test:",
    187 		args: "git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git locking/core",
    188 	},
    189 	{
    190 		body: `
    191 #syz test:
    192 git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git
    193 locking/core
    194 locking/core
    195 `,
    196 		cmd:  "test:",
    197 		args: "git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git locking/core",
    198 	},
    199 	{
    200 		body: `
    201 #syz test_5_arg_cmd arg1
    202 
    203  arg2  arg3
    204  
    205 arg4
    206 arg5
    207 `,
    208 		cmd:  "test_5_arg_cmd",
    209 		args: "arg1 arg2 arg3 arg4 arg5",
    210 	},
    211 	{
    212 		body: `
    213 #syz test_5_arg_cmd arg1
    214 arg2`,
    215 		cmd:  "test_5_arg_cmd",
    216 		args: "arg1 arg2",
    217 	},
    218 	{
    219 		body: `
    220 #syz test_5_arg_cmd arg1
    221 arg2
    222 `,
    223 		cmd:  "test_5_arg_cmd",
    224 		args: "arg1 arg2",
    225 	},
    226 	{
    227 		body: `
    228 #syz test_5_arg_cmd arg1
    229 arg2
    230 
    231  
    232 `,
    233 		cmd:  "test_5_arg_cmd",
    234 		args: "arg1 arg2",
    235 	},
    236 	{
    237 		body: `
    238 #syz fix:
    239 arg1 arg2 arg3
    240 arg4 arg5
    241  
    242 `,
    243 		cmd:  "fix:",
    244 		args: "arg1 arg2 arg3",
    245 	},
    246 	{
    247 		body: `
    248 #syz  fix: arg1 arg2 arg3
    249 arg4 arg5 
    250 `,
    251 		cmd:  "fix:",
    252 		args: "arg1 arg2 arg3",
    253 	},
    254 }
    255 
    256 type ParseTest struct {
    257 	email string
    258 	res   Email
    259 }
    260 
    261 // nolint: lll
    262 var parseTests = []ParseTest{
    263 	{`Date: Sun, 7 May 2017 19:54:00 -0700
    264 Message-ID: <123>
    265 Subject: test subject
    266 From: Bob <bob (a] example.com>
    267 To: syzbot <foo+4564456 (a] bar.com>
    268 Content-Type: text/plain; charset="UTF-8"
    269 
    270 text body
    271 second line
    272 #syz fix: 	 arg1 arg2 arg3 	
    273 last line
    274 -- 
    275 You received this message because you are subscribed to the Google Groups "syzkaller" group.
    276 To unsubscribe from this group and stop receiving emails from it, send an email to syzkaller+unsubscribe (a] googlegroups.com.
    277 To post to this group, send email to syzkaller (a] googlegroups.com.
    278 To view this discussion on the web visit https://groups.google.com/d/msgid/syzkaller/abcdef@google.com.
    279 For more options, visit https://groups.google.com/d/optout.`,
    280 		Email{
    281 			BugID:     "4564456",
    282 			MessageID: "<123>",
    283 			Link:      "https://groups.google.com/d/msgid/syzkaller/abcdef@google.com",
    284 			Subject:   "test subject",
    285 			From:      "\"Bob\" <bob (a] example.com>",
    286 			Cc:        []string{"bob (a] example.com"},
    287 			Body: `text body
    288 second line
    289 #syz fix: 	 arg1 arg2 arg3 	
    290 last line
    291 -- 
    292 You received this message because you are subscribed to the Google Groups "syzkaller" group.
    293 To unsubscribe from this group and stop receiving emails from it, send an email to syzkaller+unsubscribe (a] googlegroups.com.
    294 To post to this group, send email to syzkaller (a] googlegroups.com.
    295 To view this discussion on the web visit https://groups.google.com/d/msgid/syzkaller/abcdef@google.com.
    296 For more options, visit https://groups.google.com/d/optout.`,
    297 			Patch:       "",
    298 			Command:     "fix:",
    299 			CommandArgs: "arg1 arg2 arg3",
    300 		}},
    301 
    302 	{`Date: Sun, 7 May 2017 19:54:00 -0700
    303 Message-ID: <123>
    304 Subject: test subject
    305 From: syzbot <foo+4564456 (a] bar.com>
    306 To: Bob <bob (a] example.com>
    307 Content-Type: text/plain; charset="UTF-8"
    308 
    309 text body
    310 last line`,
    311 		Email{
    312 			BugID:     "4564456",
    313 			MessageID: "<123>",
    314 			Subject:   "test subject",
    315 			From:      "\"syzbot\" <foo+4564456 (a] bar.com>",
    316 			Cc:        []string{"bob (a] example.com"},
    317 			Body: `text body
    318 last line`,
    319 			Patch: "",
    320 		}},
    321 
    322 	{`Date: Sun, 7 May 2017 19:54:00 -0700
    323 Message-ID: <123>
    324 Subject: test subject
    325 From: Bob <bob (a] example.com>
    326 To: syzbot <bot (a] example.com>, Alice <alice (a] example.com>
    327 
    328 #syz  invalid   	 
    329 text body
    330 second line
    331 last line`,
    332 		Email{
    333 			MessageID: "<123>",
    334 			Subject:   "test subject",
    335 			From:      "\"Bob\" <bob (a] example.com>",
    336 			Cc:        []string{"alice (a] example.com", "bob (a] example.com", "bot (a] example.com"},
    337 			Body: `#syz  invalid   	 
    338 text body
    339 second line
    340 last line`,
    341 			Patch:       "",
    342 			Command:     "invalid",
    343 			CommandArgs: "",
    344 		}},
    345 
    346 	{`Date: Sun, 7 May 2017 19:54:00 -0700
    347 Message-ID: <123>
    348 Subject: test subject
    349 From: Bob <bob (a] example.com>
    350 To: syzbot <bot (a] example.com>, Alice <alice (a] example.com>
    351 Content-Type: text/plain
    352 
    353 text body
    354 second line
    355 last line
    356 #syz command`,
    357 		Email{
    358 			MessageID: "<123>",
    359 			Subject:   "test subject",
    360 			From:      "\"Bob\" <bob (a] example.com>",
    361 			Cc:        []string{"alice (a] example.com", "bob (a] example.com", "bot (a] example.com"},
    362 			Body: `text body
    363 second line
    364 last line
    365 #syz command`,
    366 			Patch:       "",
    367 			Command:     "command",
    368 			CommandArgs: "",
    369 		}},
    370 
    371 	{`Date: Sun, 7 May 2017 19:54:00 -0700
    372 Message-ID: <123>
    373 Subject: test subject
    374 From: Bob <bob (a] example.com>
    375 To: syzbot <bot (a] example.com>
    376 Content-Type: multipart/mixed; boundary="001a114ce0b01684a6054f0d8b81"
    377 
    378 --001a114ce0b01684a6054f0d8b81
    379 Content-Type: text/plain; charset="UTF-8"
    380 
    381 body text
    382 >#syz test
    383 
    384 --001a114ce0b01684a6054f0d8b81
    385 Content-Type: text/x-patch; charset="US-ASCII"; name="patch.patch"
    386 Content-Disposition: attachment; filename="patch.patch"
    387 Content-Transfer-Encoding: base64
    388 X-Attachment-Id: f_j2gwcdoa1
    389 
    390 ZGlmZiAtLWdpdCBhL2tlcm5lbC9rY292LmMgYi9rZXJuZWwva2Nvdi5jCmluZGV4IDg1ZTU1NDZj
    391 ZDc5MS4uOTQ5ZWE0NTc0NDEyIDEwMDY0NAotLS0gYS9rZXJuZWwva2Nvdi5jCisrKyBiL2tlcm5l
    392 bC9rY292LmMKQEAgLTEyNyw3ICsxMjcsNiBAQCB2b2lkIGtjb3ZfdGFza19leGl0KHN0cnVjdCB0
    393 YXNrX3N0cnVjdCAqdCkKIAlrY292ID0gdC0+a2NvdjsKIAlpZiAoa2NvdiA9PSBOVUxMKQogCQly
    394 ZXR1cm47Ci0Jc3Bpbl9sb2NrKCZrY292LT5sb2NrKTsKIAlpZiAoV0FSTl9PTihrY292LT50ICE9
    395 IHQpKSB7CiAJCXNwaW5fdW5sb2NrKCZrY292LT5sb2NrKTsKIAkJcmV0dXJuOwo=
    396 --001a114ce0b01684a6054f0d8b81--`,
    397 		Email{
    398 			MessageID: "<123>",
    399 			Subject:   "test subject",
    400 			From:      "\"Bob\" <bob (a] example.com>",
    401 			Cc:        []string{"bob (a] example.com", "bot (a] example.com"},
    402 			Body: `body text
    403 >#syz test
    404 `,
    405 			Patch: `--- a/kernel/kcov.c
    406 +++ b/kernel/kcov.c
    407 @@ -127,7 +127,6 @@ void kcov_task_exit(struct task_struct *t)
    408  	kcov = t->kcov;
    409  	if (kcov == NULL)
    410  		return;
    411 -	spin_lock(&kcov->lock);
    412  	if (WARN_ON(kcov->t != t)) {
    413  		spin_unlock(&kcov->lock);
    414  		return;
    415 `,
    416 			Command:     "",
    417 			CommandArgs: "",
    418 		}},
    419 
    420 	{`Date: Sun, 7 May 2017 19:54:00 -0700
    421 Message-ID: <123>
    422 Subject: test subject
    423 From: Bob <bob (a] example.com>
    424 To: syzbot <bot (a] example.com>
    425 Content-Type: multipart/alternative; boundary="f403043eee70018593054f0d9f1f"
    426 
    427 --f403043eee70018593054f0d9f1f
    428 Content-Type: text/plain; charset="UTF-8"
    429 
    430 On Mon, May 8, 2017 at 6:47 PM, Bob wrote:
    431 > body text
    432 
    433 #syz test
    434 
    435 commit 59372bbf3abd5b24a7f6f676a3968685c280f955
    436 Date:   Thu Apr 27 13:54:11 2017 +0200
    437 
    438     statx: correct error handling of NULL pathname
    439 
    440     test patch.
    441 
    442 diff --git a/fs/stat.c b/fs/stat.c
    443 index 3d85747bd86e..a257b872a53d 100644
    444 --- a/fs/stat.c
    445 +++ b/fs/stat.c
    446 @@ -567,8 +567,6 @@ SYSCALL_DEFINE5(statx,
    447   return -EINVAL;
    448   if ((flags & AT_STATX_SYNC_TYPE) == AT_STATX_SYNC_TYPE)
    449   return -EINVAL;
    450 - if (!filename)
    451 - return -EINVAL;
    452  
    453   error = vfs_statx(dfd, filename, flags, &stat, mask);
    454   if (error)
    455 
    456 --f403043eee70018593054f0d9f1f
    457 Content-Type: text/html; charset="UTF-8"
    458 Content-Transfer-Encoding: quoted-printable
    459 
    460 <div dir=3D"ltr">On Mon, May 8, 2017 at 6:47 PM, Dmitry Vyukov &lt;<a href=
    461 =3D"mailto:bob (a] example.com">bob (a] example.com</a>&gt; wrote:<br>&gt; bo=
    462 dy text<br><br>#syz test<br><br><div><div>commit 59372bbf3abd5b24a7f6f67=
    463 6a3968685c280f955</div><div>Date: =C2=A0 Thu Apr 27 13:54:11 2017 +0200</di=
    464 v><div><br></div><div>=C2=A0 =C2=A0 statx: correct error handling of NULL p=
    465 athname</div><div>=C2=A0 =C2=A0=C2=A0</div><div>=C2=A0 =C2=A0 test patch.</=
    466 div><div><br></div><div>diff --git a/fs/stat.c b/fs/stat.c</div><div>index =
    467 3d85747bd86e..a257b872a53d 100644</div><div>--- a/fs/stat.c</div><div>+++ b=
    468 /fs/stat.c</div><div>@@ -567,8 +567,6 @@ SYSCALL_DEFINE5(statx,</div><div>=
    469 =C2=A0<span class=3D"gmail-Apple-tab-span" style=3D"white-space:pre">=09=09=
    470 </span>return -EINVAL;</div><div>=C2=A0<span class=3D"gmail-Apple-tab-span"=
    471  style=3D"white-space:pre">=09</span>if ((flags &amp; AT_STATX_SYNC_TYPE) =
    472 =3D=3D AT_STATX_SYNC_TYPE)</div><div>=C2=A0<span class=3D"gmail-Apple-tab-s=
    473 pan" style=3D"white-space:pre">=09=09</span>return -EINVAL;</div><div>-<spa=
    474 n class=3D"gmail-Apple-tab-span" style=3D"white-space:pre">=09</span>if (!f=
    475 ilename)</div><div>-<span class=3D"gmail-Apple-tab-span" style=3D"white-spa=
    476 ce:pre">=09=09</span>return -EINVAL;</div><div>=C2=A0</div><div>=C2=A0<span=
    477  class=3D"gmail-Apple-tab-span" style=3D"white-space:pre">=09</span>error =
    478 =3D vfs_statx(dfd, filename, flags, &amp;stat, mask);</div><div>=C2=A0<span=
    479  class=3D"gmail-Apple-tab-span" style=3D"white-space:pre">=09</span>if (err=
    480 or)</div></div></div>
    481 
    482 --f403043eee70018593054f0d9f1f--`,
    483 		Email{
    484 			MessageID: "<123>",
    485 			Subject:   "test subject",
    486 			From:      "\"Bob\" <bob (a] example.com>",
    487 			Cc:        []string{"bob (a] example.com", "bot (a] example.com"},
    488 			Body: `On Mon, May 8, 2017 at 6:47 PM, Bob wrote:
    489 > body text
    490 
    491 #syz test
    492 
    493 commit 59372bbf3abd5b24a7f6f676a3968685c280f955
    494 Date:   Thu Apr 27 13:54:11 2017 +0200
    495 
    496     statx: correct error handling of NULL pathname
    497 
    498     test patch.
    499 
    500 diff --git a/fs/stat.c b/fs/stat.c
    501 index 3d85747bd86e..a257b872a53d 100644
    502 --- a/fs/stat.c
    503 +++ b/fs/stat.c
    504 @@ -567,8 +567,6 @@ SYSCALL_DEFINE5(statx,
    505   return -EINVAL;
    506   if ((flags & AT_STATX_SYNC_TYPE) == AT_STATX_SYNC_TYPE)
    507   return -EINVAL;
    508 - if (!filename)
    509 - return -EINVAL;
    510  
    511   error = vfs_statx(dfd, filename, flags, &stat, mask);
    512   if (error)
    513 `,
    514 			Patch: `--- a/fs/stat.c
    515 +++ b/fs/stat.c
    516 @@ -567,8 +567,6 @@ SYSCALL_DEFINE5(statx,
    517   return -EINVAL;
    518   if ((flags & AT_STATX_SYNC_TYPE) == AT_STATX_SYNC_TYPE)
    519   return -EINVAL;
    520 - if (!filename)
    521 - return -EINVAL;
    522  
    523   error = vfs_statx(dfd, filename, flags, &stat, mask);
    524   if (error)
    525 `,
    526 			Command:     "test",
    527 			CommandArgs: "",
    528 		}},
    529 
    530 	{`Sender: syzkaller-bugs (a] googlegroups.com
    531 Subject: Re: BUG: unable to handle kernel NULL pointer dereference in
    532  sock_poll
    533 To: syzbot <syzbot+344bb0f46d7719cd9483 (a] syzkaller.appspotmail.com>
    534 From: bar <bar (a] foo.com>
    535 Message-ID: <1250334f-7220-2bff-5d87-b87573758d81 (a] bar.com>
    536 Date: Sun, 10 Jun 2018 10:38:20 +0900
    537 MIME-Version: 1.0
    538 Content-Type: text/plain; charset="UTF-8"
    539 Content-Language: en-US
    540 Content-Transfer-Encoding: quoted-printable
    541 
    542 On 2018/06/10 4:57, syzbot wrote:
    543 > Hello,
    544 >=20
    545 > syzbot found the following crash on:
    546 >=20
    547 > HEAD commit: 7d3bf613e99a Merge tag 'libnvdimm-for-4.18=
    548 ' of git://git.k..
    549 > git tree: upstream
    550 > console output: https://syzkaller.appspot.com/x/log.txt?x=3D1188a05f80000=
    551 0
    552 > kernel config: https://syzkaller.appspot.com/x/.config?x=3Df04d8d0a=
    553 2afb789a
    554 
    555 #syz dup: BUG: unable to handle kernel NULL pointer dereference in corrupte=
    556 d
    557 `, Email{
    558 		MessageID: "<1250334f-7220-2bff-5d87-b87573758d81 (a] bar.com>",
    559 		Subject:   "Re: BUG: unable to handle kernel NULL pointer dereference in sock_poll",
    560 		From:      "\"bar\" <bar (a] foo.com>",
    561 		Cc:        []string{"bar (a] foo.com", "syzbot (a] syzkaller.appspotmail.com"},
    562 		Body: `On 2018/06/10 4:57, syzbot wrote:
    563 > Hello,
    564 > 
    565 > syzbot found the following crash on:
    566 > 
    567 > HEAD commit: 7d3bf613e99a Merge tag 'libnvdimm-for-4.18' of git://git.k..
    568 > git tree: upstream
    569 > console output: https://syzkaller.appspot.com/x/log.txt?x=1188a05f800000
    570 > kernel config: https://syzkaller.appspot.com/x/.config?x=f04d8d0a2afb789a
    571 
    572 #syz dup: BUG: unable to handle kernel NULL pointer dereference in corrupted
    573 `,
    574 		Command:     "dup:",
    575 		CommandArgs: "BUG: unable to handle kernel NULL pointer dereference in corrupted",
    576 	}},
    577 }
    578